Merge changes from topics "isCommunalProfileAPI", "isForegroundUserAnAdmin" into main

* changes:
  isForegroundUserAdmin public API
  Public isCommunalProfile() API
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 864caf4..e3cbd92 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -14,12 +14,19 @@
 
 aconfig_srcjars = [
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
+    ":android.companion.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
+    ":android.content.res.flags-aconfig-java{.generated_srcjars}",
+    ":android.hardware.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+    ":android.location.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.security.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
     ":android.view.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
     ":camera_platform_flags_core_java_lib{.generated_srcjars}",
@@ -32,17 +39,24 @@
     ":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.audio.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}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+    ":aconfig_midi_flags_java_lib{.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}",
 ]
 
 filegroup {
@@ -132,6 +146,21 @@
     aconfig_declarations: "com.android.text.flags-aconfig",
 }
 
+// Location
+aconfig_declarations {
+    name: "android.location.flags-aconfig",
+    package: "android.location.flags",
+    srcs: [
+        "location/java/android/location/flags/*.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "android.location.flags-aconfig-java",
+    aconfig_declarations: "android.location.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // NFC
 aconfig_declarations {
     name: "android.nfc.flags-aconfig",
@@ -142,6 +171,11 @@
 java_aconfig_library {
     name: "android.nfc.flags-aconfig-java",
     aconfig_declarations: "android.nfc.flags-aconfig",
+    min_sdk_version: "VanillaIceCream",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+    ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -266,6 +300,19 @@
     aconfig_declarations: "android.view.accessibility.flags-aconfig",
 }
 
+// Hardware
+aconfig_declarations {
+    name: "android.hardware.flags-aconfig",
+    package: "android.hardware.flags",
+    srcs: ["core/java/android/hardware/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.hardware.flags-aconfig-java",
+    aconfig_declarations: "android.hardware.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Widget
 aconfig_declarations {
     name: "android.widget.flags-aconfig",
@@ -279,6 +326,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+rust_aconfig_library {
+    name: "libandroid_security_flags_rust",
+    crate_name: "android_security_flags",
+    aconfig_declarations: "android.security.flags-aconfig",
+}
+
 // Package Manager
 aconfig_declarations {
     name: "android.content.pm.flags-aconfig",
@@ -292,6 +345,26 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.content.pm.flags-aconfig-java-host",
+    aconfig_declarations: "android.content.pm.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Resources
+aconfig_declarations {
+    name: "android.content.res.flags-aconfig",
+    package: "android.content.res",
+    srcs: ["core/java/android/content/res/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.content.res.flags-aconfig-java",
+    aconfig_declarations: "android.content.res.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
@@ -305,6 +378,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Media Audio
+java_aconfig_library {
+    name: "com.android.media.audio.flags-aconfig-java",
+    aconfig_declarations: "aconfig_audio_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Permissions
 aconfig_declarations {
     name: "android.permission.flags-aconfig",
@@ -316,6 +396,24 @@
     name: "android.permission.flags-aconfig-java",
     aconfig_declarations: "android.permission.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.permission",
+    ],
+
+}
+
+// SQLite
+aconfig_declarations {
+    name: "android.database.sqlite-aconfig",
+    package: "android.database.sqlite",
+    srcs: ["core/java/android/database/sqlite/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.database.sqlite-aconfig-java",
+    aconfig_declarations: "android.database.sqlite-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
 // Biometrics
@@ -338,6 +436,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "framework_graphics_flags_java_lib",
+    aconfig_declarations: "framework_graphics_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Display
 java_aconfig_library {
     name: "display_flags_lib",
@@ -345,6 +449,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "com.android.internal.foldables.flags-aconfig-java",
+    aconfig_declarations: "fold_lock_setting_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Multi user
 aconfig_declarations {
     name: "android.multiuser.flags-aconfig",
@@ -429,7 +539,7 @@
     package: "android.service.autofill",
     srcs: [
         "services/autofill/bugfixes.aconfig",
-        "services/autofill/features.aconfig"
+        "services/autofill/features.aconfig",
     ],
 }
 
@@ -438,3 +548,82 @@
     aconfig_declarations: "android.service.autofill.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Companion
+aconfig_declarations {
+    name: "android.companion.flags-aconfig",
+    package: "android.companion",
+    srcs: ["core/java/android/companion/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.companion.flags-aconfig-java",
+    aconfig_declarations: "android.companion.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// CoreNetworking
+java_aconfig_library {
+    name: "com.android.net.flags-aconfig-java",
+    aconfig_declarations: "com.android.net.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// VCN
+aconfig_declarations {
+    name: "android.net.vcn.flags-aconfig",
+    package: "android.net.vcn",
+    srcs: ["core/java/android/net/vcn/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.net.vcn.flags-aconfig-java",
+    aconfig_declarations: "android.net.vcn.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// DevicePolicy
+aconfig_declarations {
+    name: "device_policy_aconfig_flags",
+    package: "android.app.admin.flags",
+    srcs: [
+        "core/java/android/app/admin/flags/flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "device_policy_aconfig_flags_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+cc_aconfig_library {
+    name: "device_policy_aconfig_flags_c_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+}
+
+// Notifications
+aconfig_declarations {
+    name: "android.service.notification.flags-aconfig",
+    package: "android.service.notification",
+    srcs: ["core/java/android/service/notification/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.notification.flags-aconfig-java",
+    aconfig_declarations: "android.service.notification.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Smartspace
+aconfig_declarations {
+    name: "android.app.smartspace.flags-aconfig",
+    package: "android.app.smartspace.flags",
+    srcs: ["core/java/android/app/smartspace/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.smartspace.flags-aconfig-java",
+    aconfig_declarations: "android.app.smartspace.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index b1b332a..b5f7e99 100644
--- a/Android.bp
+++ b/Android.bp
@@ -64,6 +64,7 @@
     srcs: [
         // Java/AIDL sources under frameworks/base
         ":framework-annotations",
+        ":ravenwood-annotations",
         ":framework-blobstore-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
@@ -226,7 +227,6 @@
         "android.hardware.radio.messaging-V3-java",
         "android.hardware.radio.modem-V3-java",
         "android.hardware.radio.network-V3-java",
-        "android.hardware.radio.satellite-V1-java",
         "android.hardware.radio.sim-V3-java",
         "android.hardware.radio.voice-V3-java",
         "android.hardware.thermal-V1.0-java-constants",
@@ -284,6 +284,7 @@
         enforce_permissions_exceptions: [
             // Do not add entries to this list.
             ":framework-annotations",
+            ":ravenwood-annotations",
             ":framework-blobstore-sources",
             ":framework-core-sources",
             ":framework-drm-sources",
@@ -322,7 +323,6 @@
             ":installd_aidl",
             ":libaudioclient_aidl",
             ":libbinder_aidl",
-            ":libbluetooth-binder-aidl",
             ":libcamera_client_aidl",
             ":libcamera_client_framework_aidl",
             ":libupdate_engine_aidl",
@@ -409,7 +409,6 @@
         "audiopolicy-aidl-java",
         "sounddose-aidl-java",
         "modules-utils-expresslog",
-        "hoststubgen-annotations",
     ],
 }
 
@@ -615,30 +614,6 @@
     ],
 }
 
-// TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
-metalava_framework_docs_args = "" +
-    "--api-lint-ignore-prefix android.icu. " +
-    "--api-lint-ignore-prefix java. " +
-    "--api-lint-ignore-prefix junit. " +
-    "--api-lint-ignore-prefix org. " +
-    "--error NoSettingsProvider " +
-    "--error UnhiddenSystemApi " +
-    "--error UnflaggedApi " +
-    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " +
-    "--hide BroadcastBehavior " +
-    "--hide CallbackInterface " +
-    "--hide DeprecationMismatch " +
-    "--hide HiddenSuperclass " +
-    "--hide HiddenTypeParameter " +
-    "--hide MissingPermission " +
-    "--hide-package android.audio.policy.configuration.V7_0 " +
-    "--hide-package com.android.server " +
-    "--hide RequiresPermission " +
-    "--hide SdkConstant " +
-    "--hide Todo " +
-    "--hide UnavailableSymbol " +
-    "--manifest $(location :frameworks-base-core-AndroidManifest.xml) "
-
 packages_to_document = [
     "android",
     "dalvik",
@@ -707,6 +682,27 @@
         "android.hardware.vibrator-V1.3-java",
         "framework-protos",
     ],
+    flags: [
+        "--api-lint-ignore-prefix android.icu.",
+        "--api-lint-ignore-prefix java.",
+        "--api-lint-ignore-prefix junit.",
+        "--api-lint-ignore-prefix org.",
+        "--error NoSettingsProvider",
+        "--error UnhiddenSystemApi",
+        "--error UnflaggedApi",
+        "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*",
+        "--hide BroadcastBehavior",
+        "--hide CallbackInterface",
+        "--hide DeprecationMismatch",
+        "--hide HiddenSuperclass",
+        "--hide MissingPermission",
+        "--hide RequiresPermission",
+        "--hide SdkConstant",
+        "--hide Todo",
+        "--hide-package android.audio.policy.configuration.V7_0",
+        "--hide-package com.android.server",
+        "--manifest $(location :frameworks-base-core-AndroidManifest.xml)",
+    ],
     filter_packages: packages_to_document,
     high_mem: true, // Lots of sources => high memory use, see b/170701554
     installable: false,
@@ -838,4 +834,5 @@
     "AconfigFlags.bp",
     "ProtoLibraries.bp",
     "TestProtoLibraries.bp",
+    "Ravenwood.bp",
 ]
diff --git a/Android.mk b/Android.mk
index d9e202c..e2c1ed8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -69,9 +69,6 @@
 .PHONY: framework-doc-stubs
 framework-doc-stubs: $(SDK_METADATA)
 
-# Run this for checkbuild
-checkbuild: doc-comment-check-docs
-
 # Include subdirectory makefiles
 # ============================================================
 
diff --git a/OWNERS b/OWNERS
index 4e5c7d8..023bdef 100644
--- a/OWNERS
+++ b/OWNERS
@@ -34,3 +34,6 @@
 
 per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS
 per-file SQLITE_OWNERS = file:/SQLITE_OWNERS
+
+per-file *ravenwood* = file:ravenwood/OWNERS
+per-file *Ravenwood* = file:ravenwood/OWNERS
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index bded26a..b42f7bc 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,6 +25,9 @@
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
-ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES}
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
+
+# This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
+flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 45bb161..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@
     output_extension: "proto.h",
 }
 
+// ====  nfc framework java library  ==============================
+gensrcs {
+    name: "framework-nfc-javastream-protos",
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+
+    cmd: "mkdir -p $(genDir)/$(in) " +
+        "&& $(location aprotoc) " +
+        "  --plugin=$(location protoc-gen-javastream) " +
+        "  --javastream_out=$(genDir)/$(in) " +
+        "  -Iexternal/protobuf/src " +
+        "  -I . " +
+        "  $(in) " +
+        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+    srcs: [
+        "core/proto/android/app/pendingintent.proto",
+        "core/proto/android/content/component_name.proto",
+        "core/proto/android/content/intent.proto",
+        "core/proto/android/nfc/*.proto",
+        "core/proto/android/os/patternmatcher.proto",
+        "core/proto/android/os/persistablebundle.proto",
+        "core/proto/android/privacy.proto",
+    ],
+
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+
+    output_extension: "srcjar",
+}
+
 // ====  java proto host library  ==============================
 java_library_host {
     name: "platformprotos",
diff --git a/Ravenwood.bp b/Ravenwood.bp
new file mode 100644
index 0000000..da02298
--- /dev/null
+++ b/Ravenwood.bp
@@ -0,0 +1,70 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// We need this "trampoline" rule to force soong to give a host-side jar to
+// framework-minus-apex.ravenwood. Otherwise, soong would mix up the arch (?) and we'd get
+// a dex jar.
+java_library {
+    name: "framework-minus-apex-for-hoststubgen",
+    installable: false, // host only jar.
+    static_libs: [
+        "framework-minus-apex",
+    ],
+    sdk_version: "core_platform",
+    visibility: ["//visibility:private"],
+}
+
+// Generate the stub/impl from framework-all, with hidden APIs.
+java_genrule_host {
+    name: "framework-minus-apex.ravenwood-base",
+    tools: ["hoststubgen"],
+    cmd: "$(location hoststubgen) " +
+        "@$(location :ravenwood-standard-options) " +
+
+        "--out-stub-jar $(location ravenwood_stub.jar) " +
+        "--out-impl-jar $(location ravenwood.jar) " +
+
+        "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
+        "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+
+        "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
+        "--policy-override-file $(location framework-minus-apex-ravenwood-policies.txt) ",
+    srcs: [
+        ":framework-minus-apex-for-hoststubgen",
+        "framework-minus-apex-ravenwood-policies.txt",
+        ":ravenwood-standard-options",
+    ],
+    out: [
+        "ravenwood.jar",
+        "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional.
+
+        // Following files are created just as FYI.
+        "hoststubgen_keep_all.txt",
+        "hoststubgen_dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
+java_genrule_host {
+    name: "framework-minus-apex.ravenwood",
+    defaults: ["hoststubgen-for-prototype-only-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-minus-apex.ravenwood-base{ravenwood.jar}",
+    ],
+    out: [
+        "framework-minus-apex.ravenwood.jar",
+    ],
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index f1403bd5..e833bb9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -439,7 +439,7 @@
      * provides an easy way to tell whether the job is being executed due to the deadline
      * expiring. Note: If the job is running because its deadline expired, it implies that its
      * constraints will not be met. However,
-     * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever
+     * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever
      * run when their constraints are satisfied, therefore, the constraints will still be satisfied
      * for a periodic job even if the deadline has expired.
      */
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 4e3cb7d..eb5502b 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "relax_prefetch_connectivity_constraint_only_on_charger"
-    namespace: "backstagepower"
-    description: "Only relax a prefetch job's connectivity constraint when the device is charging"
+    namespace: "backstage_power"
+    description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
     bug: "299329948"
 }
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 3cbee5d0..384a480 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -173,8 +173,6 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
-import libcore.util.EmptyArray;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -260,7 +258,7 @@
     /**
      * A map from uid to the last op-mode we have seen for
      * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change
-     * when the denylist changes.
+     * when the app-op changes.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -671,9 +669,6 @@
     @VisibleForTesting
     final class Constants implements DeviceConfig.OnPropertiesChangedListener,
             EconomyManagerInternal.TareStateChangeListener {
-        @VisibleForTesting
-        static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250;
-
         // Key names stored in the settings value.
         @VisibleForTesting
         static final String KEY_MIN_FUTURITY = "min_futurity";
@@ -727,8 +722,6 @@
         @VisibleForTesting
         static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";
         @VisibleForTesting
-        static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list";
-        @VisibleForTesting
         static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz";
         @VisibleForTesting
         static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz";
@@ -835,13 +828,6 @@
         public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;
 
         /**
-         * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for
-         * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can
-         * be accessed without synchronizing on {@link #mLock}.
-         */
-        public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet();
-
-        /**
          * Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent
          * WAKE_FROM_IDLE alarm.
          */
@@ -1025,21 +1011,6 @@
                             PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,
                                     DEFAULT_PRIORITY_ALARM_DELAY);
                             break;
-                        case KEY_EXACT_ALARM_DENY_LIST:
-                            final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST,
-                                    "");
-                            final String[] values = rawValue.isEmpty()
-                                    ? EmptyArray.STRING
-                                    : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1);
-                            if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) {
-                                Slog.w(TAG, "Deny list too long, truncating to "
-                                        + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements.");
-                                updateExactAlarmDenyList(
-                                        Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE));
-                            } else {
-                                updateExactAlarmDenyList(values);
-                            }
-                            break;
                         case KEY_MIN_DEVICE_IDLE_FUZZ:
                         case KEY_MAX_DEVICE_IDLE_FUZZ:
                             if (!deviceIdleFuzzBoundariesUpdated) {
@@ -1110,28 +1081,6 @@
             }
         }
 
-        private void updateExactAlarmDenyList(String[] newDenyList) {
-            final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList));
-            final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST);
-            final Set<String> added = new ArraySet<>(newDenyList);
-
-            added.removeAll(EXACT_ALARM_DENY_LIST);
-            removed.removeAll(newSet);
-            if (added.size() > 0) {
-                mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added)
-                        .sendToTarget();
-            }
-            if (removed.size() > 0) {
-                mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed)
-                        .sendToTarget();
-            }
-            if (newDenyList.length == 0) {
-                EXACT_ALARM_DENY_LIST = Collections.emptySet();
-            } else {
-                EXACT_ALARM_DENY_LIST = newSet;
-            }
-        }
-
         private void updateDeviceIdleFuzzBoundaries() {
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_ALARM_MANAGER,
@@ -1277,9 +1226,6 @@
             TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);
             pw.println();
 
-            pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST);
-            pw.println();
-
             pw.print(KEY_MIN_DEVICE_IDLE_FUZZ);
             pw.print("=");
             TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw);
@@ -2114,14 +2060,10 @@
                                             ? permissionState
                                             : (newMode == AppOpsManager.MODE_ALLOWED);
                                 } else {
-                                    final boolean allowedByDefault =
-                                            !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
                                     hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
-                                            ? allowedByDefault
-                                            : (oldMode == AppOpsManager.MODE_ALLOWED);
+                                            || (oldMode == AppOpsManager.MODE_ALLOWED);
                                     hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
-                                            ? allowedByDefault
-                                            : (newMode == AppOpsManager.MODE_ALLOWED);
+                                            || (newMode == AppOpsManager.MODE_ALLOWED);
                                 }
 
                                 if (hadPermission && !hasPermission) {
@@ -2769,11 +2711,8 @@
             // Compatibility permission check for older apps.
             final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
                     packageName);
-            if (mode == AppOpsManager.MODE_DEFAULT) {
-                hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
-            } else {
-                hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
-            }
+            hasPermission = (mode == AppOpsManager.MODE_DEFAULT)
+                    || (mode == AppOpsManager.MODE_ALLOWED);
         }
         mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start);
         return hasPermission;
@@ -3993,63 +3932,6 @@
     }
 
     /**
-     * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that
-     * either got added or deleted.
-     * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission.
-     *
-     * Note that these packages don't need to be installed on the device, but if they are and they
-     * do undergo a permission change, we will handle them appropriately.
-     *
-     * This should not be called with the lock held as it calls out to other services.
-     * This is not expected to get called frequently.
-     */
-    void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) {
-        Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from")
-                + " the exact alarm deny list.");
-
-        final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds();
-
-        for (int i = 0; i < changedPackages.size(); i++) {
-            final String changedPackage = changedPackages.valueAt(i);
-            for (final int userId : startedUserIds) {
-                final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId);
-                if (uid <= 0) {
-                    continue;
-                }
-                if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
-                    continue;
-                }
-                if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) {
-                    continue;
-                }
-                if (hasUseExactAlarmInternal(changedPackage, uid)) {
-                    continue;
-                }
-                if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) {
-                    // Permission isn't requested, deny list doesn't matter.
-                    continue;
-                }
-                final int appOpMode;
-                synchronized (mLock) {
-                    appOpMode = mLastOpScheduleExactAlarm.get(uid,
-                            AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM));
-                }
-                if (appOpMode != AppOpsManager.MODE_DEFAULT) {
-                    // Deny list doesn't matter.
-                    continue;
-                }
-                // added: true => package was added to the deny list
-                // added: false => package was removed from the deny list
-                if (added) {
-                    removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true);
-                } else {
-                    sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
-                }
-            }
-        }
-    }
-
-    /**
      * Called when an app loses the permission to use exact alarms. This will happen when the app
      * no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
      * {@link Manifest.permission#USE_EXACT_ALARM}.
@@ -4931,8 +4813,8 @@
         public static final int CHARGING_STATUS_CHANGED = 6;
         public static final int REMOVE_FOR_CANCELED = 7;
         public static final int REMOVE_EXACT_ALARMS = 8;
-        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9;
-        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
+        // Unused id 9
+        // Unused id 10
         public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
         public static final int TARE_AFFORDABILITY_CHANGED = 12;
         public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
@@ -5041,12 +4923,6 @@
                     String packageName = (String) msg.obj;
                     removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true);
                     break;
-                case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
-                    handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true);
-                    break;
-                case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED:
-                    handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false);
-                    break;
                 case REFRESH_EXACT_ALARM_CANDIDATES:
                     refreshExactAlarmCandidates();
                     break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 5bf2eb9..6550f26 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1518,8 +1518,8 @@
             @WorkType final int workType) {
         final List<StateController> controllers = mService.mControllers;
         final int numControllers = controllers.size();
-        final PowerManager.WakeLock wl =
-                mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getTag());
+        final PowerManager.WakeLock wl = mPowerManager.newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getWakelockTag());
         wl.setWorkSource(mService.deriveWorkSource(
                 jobStatus.getSourceUid(), jobStatus.getSourcePackageName()));
         wl.setReferenceCounted(false);
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 a143d6f..592aff8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1555,7 +1555,7 @@
 
     private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
 
-    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
+    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int callingUid, String packageName,
             int userId, @Nullable String namespace, String tag) {
         // Rate limit excessive schedule() calls.
         final String servicePkg = job.getService().getPackageName();
@@ -1608,12 +1608,12 @@
             mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
         }
 
-        if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
-            Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+        if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, servicePkg)) {
+            Slog.w(TAG, "Not scheduling job for " + callingUid + ":" + job.toString()
                     + " -- package not allowed to start");
             Counter.logIncrementWithUid(
                     "job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled",
-                    uId);
+                    callingUid);
             return JobScheduler.RESULT_FAILURE;
         }
 
@@ -1623,7 +1623,7 @@
                             job.getEstimatedNetworkDownloadBytes()));
             sInitialJobEstimatedNetworkUploadKBLogger.logSample(
                     safelyScaleBytesToKBForHistogram(job.getEstimatedNetworkUploadBytes()));
-            sJobMinimumChunkKBLogger.logSampleWithUid(uId,
+            sJobMinimumChunkKBLogger.logSampleWithUid(callingUid,
                     safelyScaleBytesToKBForHistogram(job.getMinimumNetworkChunkBytes()));
             if (work != null) {
                 sInitialJwiEstimatedNetworkDownloadKBLogger.logSample(
@@ -1632,7 +1632,7 @@
                 sInitialJwiEstimatedNetworkUploadKBLogger.logSample(
                         safelyScaleBytesToKBForHistogram(
                                 work.getEstimatedNetworkUploadBytes()));
-                sJwiMinimumChunkKBLogger.logSampleWithUid(uId,
+                sJwiMinimumChunkKBLogger.logSampleWithUid(callingUid,
                         safelyScaleBytesToKBForHistogram(
                                 work.getMinimumNetworkChunkBytes()));
             }
@@ -1640,11 +1640,12 @@
 
         if (work != null) {
             Counter.logIncrementWithUid(
-                    "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", uId);
+                    "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", callingUid);
         }
 
         synchronized (mLock) {
-            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
+            final JobStatus toCancel =
+                    mJobs.getJobByUidAndJobId(callingUid, namespace, job.getId());
 
             if (work != null && toCancel != null) {
                 // Fast path: we are adding work to an existing job, and the JobInfo is not
@@ -1664,7 +1665,7 @@
                     // TODO(273758274): improve JobScheduler's resilience and memory management
                     if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
                             && toCancel.isPersisted()) {
-                        Slog.w(TAG, "Too many JWIs for uid " + uId);
+                        Slog.w(TAG, "Too many JWIs for uid " + callingUid);
                         throw new IllegalStateException("Apps may not persist more than "
                                 + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
                                 + " JobWorkItems per job");
@@ -1682,7 +1683,8 @@
                                         | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
                     }
                     mJobs.touchJob(toCancel);
-                    sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount());
+                    sEnqueuedJwiHighWaterMarkLogger
+                            .logSampleWithUid(callingUid, toCancel.getWorkCount());
 
                     // If any of work item is enqueued when the source is in the foreground,
                     // exempt the entire job.
@@ -1692,8 +1694,8 @@
                 }
             }
 
-            JobStatus jobStatus =
-                    JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag);
+            JobStatus jobStatus = JobStatus.createFromJobInfo(
+                    job, callingUid, packageName, userId, namespace, tag);
 
             // Return failure early if expedited job quota used up.
             if (jobStatus.isRequestedExpeditedJob()) {
@@ -1702,7 +1704,7 @@
                         && !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
                     Counter.logIncrementWithUid(
                             "job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota",
-                            uId);
+                            callingUid);
                     return JobScheduler.RESULT_FAILURE;
                 }
             }
@@ -1716,10 +1718,10 @@
             if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
             // Jobs on behalf of others don't apply to the per-app job cap
             if (packageName == null) {
-                if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
-                    Slog.w(TAG, "Too many jobs for uid " + uId);
+                if (mJobs.countJobsForUid(callingUid) > MAX_JOBS_PER_APP) {
+                    Slog.w(TAG, "Too many jobs for uid " + callingUid);
                     Counter.logIncrementWithUid(
-                            "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", uId);
+                            "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", callingUid);
                     throw new IllegalStateException("Apps may not schedule more than "
                             + MAX_JOBS_PER_APP + " distinct jobs");
                 }
@@ -1743,7 +1745,7 @@
                 // TODO(273758274): improve JobScheduler's resilience and memory management
                 if (work != null && toCancel.isPersisted()
                         && toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) {
-                    Slog.w(TAG, "Too many JWIs for uid " + uId);
+                    Slog.w(TAG, "Too many JWIs for uid " + callingUid);
                     throw new IllegalStateException("Apps may not persist more than "
                             + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
                             + " JobWorkItems per job");
@@ -1759,13 +1761,14 @@
             if (work != null) {
                 // If work has been supplied, enqueue it into the new job.
                 jobStatus.enqueueWorkLocked(work);
-                sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, jobStatus.getWorkCount());
+                sEnqueuedJwiHighWaterMarkLogger
+                        .logSampleWithUid(callingUid, jobStatus.getWorkCount());
             }
 
-            final int sourceUid = uId;
+            final int sourceUid = jobStatus.getSourceUid();
             FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                     jobStatus.isProxyJob()
-                            ? new int[]{sourceUid, jobStatus.getUid()} : new int[]{sourceUid},
+                            ? new int[]{sourceUid, callingUid} : new int[]{sourceUid},
                     // Given that the source tag is set by the calling app, it should be connected
                     // to the calling app in the attribution for a proxied job.
                     jobStatus.isProxyJob()
@@ -5509,7 +5512,6 @@
                 pw.print("Evaluated bias: ");
                 pw.println(JobInfo.getBiasString(bias));
 
-                pw.print("Tag: "); pw.println(job.getTag());
                 pw.print("Enq: ");
                 TimeUtils.formatDuration(job.madePending - nowUptime, pw);
                 pw.decreaseIndent();
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 4357d4f..293088d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -180,7 +180,7 @@
 
                 case "-u":
                 case "--user":
-                    userId = Integer.parseInt(getNextArgRequired());
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
                 case "-n":
@@ -199,6 +199,10 @@
             return -1;
         }
 
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+
         final String pkgName = getNextArgRequired();
         final int jobId = Integer.parseInt(getNextArgRequired());
 
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 f47766e..79653f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -398,7 +398,8 @@
             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
             job.clearPersistedUtcTimes();
 
-            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, job.getTag());
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    job.getWakelockTag());
             mWakeLock.setWorkSource(
                     mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
             mWakeLock.setReferenceCounted(false);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d48d84b..1fdf906 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -75,6 +75,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 /**
  * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
@@ -99,6 +100,8 @@
     private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L;
     @VisibleForTesting
     static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+    private static final Pattern SPLIT_FILE_PATTERN =
+            Pattern.compile("^" + JOB_FILE_SPLIT_PREFIX + "\\d+.xml$");
     private static final int ALL_UIDS = -1;
     @VisibleForTesting
     static final int INVALID_UID = -2;
@@ -1121,6 +1124,11 @@
             int numDuplicates = 0;
             synchronized (mLock) {
                 for (File file : files) {
+                    if (!file.getName().equals("jobs.xml")
+                            && !SPLIT_FILE_PATTERN.matcher(file.getName()).matches()) {
+                        // Skip temporary or other files.
+                        continue;
+                    }
                     final AtomicFile aFile = createJobFile(file);
                     try (FileInputStream fis = aFile.openRead()) {
                         jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 45f15db..63eaa63 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -932,7 +932,9 @@
             return false;
         }
         if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
-            if (!mService.isBatteryCharging()) {
+            // Since the constraint relaxation isn't required by the job, only do it when the
+            // device is charging and the battery level is above the "low battery" threshold.
+            if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) {
                 return false;
             }
         }
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 458ff35..1fb54d5 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
@@ -253,7 +253,12 @@
     /** An ID that can be used to uniquely identify the job when logging statsd metrics. */
     private final long mLoggingJobId;
 
-    final String tag;
+    /**
+     * 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.
+     */
+    @Nullable
+    private String mWakelockTag;
 
     /** Whether this job was scheduled by one app on behalf of another. */
     final boolean mIsProxyJob;
@@ -627,7 +632,6 @@
         this.batteryName = this.sourceTag != null
                 ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName()
                 : bnNamespace + job.getService().flattenToShortString();
-        this.tag = "*job*/" + this.batteryName + "#" + job.getId();
 
         final String componentPackage = job.getService().getPackageName();
         mIsProxyJob = !this.sourcePackageName.equals(componentPackage);
@@ -1321,8 +1325,13 @@
         return batteryName;
     }
 
-    public String getTag() {
-        return tag;
+    /** Return the String to be used as the tag for the wakelock held for this job. */
+    @NonNull
+    public String getWakelockTag() {
+        if (mWakelockTag == null) {
+            mWakelockTag = "*job*/" + this.batteryName;
+        }
+        return mWakelockTag;
     }
 
     public int getBias() {
@@ -2639,7 +2648,7 @@
     @NeverCompile // Avoid size overhead of debugging code.
     public void dump(IndentingPrintWriter pw,  boolean full, long nowElapsed) {
         UserHandle.formatUid(pw, callingUid);
-        pw.print(" tag="); pw.println(tag);
+        pw.print(" tag="); pw.println(getWakelockTag());
 
         pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
         pw.print(" user="); pw.print(getSourceUserId());
@@ -2955,7 +2964,7 @@
         final long token = proto.start(fieldId);
 
         proto.write(JobStatusDumpProto.CALLING_UID, callingUid);
-        proto.write(JobStatusDumpProto.TAG, tag);
+        proto.write(JobStatusDumpProto.TAG, getWakelockTag());
         proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
         proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
         proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 2b7438c..fdeb072 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -536,10 +536,13 @@
         static final String KEY_LAUNCH_TIME_ALLOWANCE_MS =
                 PC_CONSTANT_PREFIX + "launch_time_allowance_ms";
 
-        private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS;
-        private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 20 * MINUTE_IN_MILLIS;
+        private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = HOUR_IN_MILLIS;
+        private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 30 * MINUTE_IN_MILLIS;
 
-        /** How much time each app will have to run jobs within their standby bucket window. */
+        /**
+         * The earliest amount of time before the next estimated app launch time that we may choose
+         * to run a prefetch job for the app.
+         */
         public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
 
         /**
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 8d8fc12..5744bdf 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -20,41 +20,6 @@
 // The API doc generation is done by the various droiddoc modules each of which
 // is for different format.
 
-/////////////////////////////////////////////////////////////////////
-// stub source files are generated using metalava
-/////////////////////////////////////////////////////////////////////
-
-framework_docs_only_libs = [
-    "voip-common",
-    "android.test.mock",
-    "android-support-annotations",
-    "android-support-compat",
-    "android-support-core-ui",
-    "android-support-core-utils",
-    "android-support-design",
-    "android-support-dynamic-animation",
-    "android-support-exifinterface",
-    "android-support-fragment",
-    "android-support-media-compat",
-    "android-support-percent",
-    "android-support-transition",
-    "android-support-v7-cardview",
-    "android-support-v7-gridlayout",
-    "android-support-v7-mediarouter",
-    "android-support-v7-palette",
-    "android-support-v7-preference",
-    "android-support-v13",
-    "android-support-v14-preference",
-    "android-support-v17-leanback",
-    "android-support-vectordrawable",
-    "android-support-animatedvectordrawable",
-    "android-support-v7-appcompat",
-    "android-support-v7-recyclerview",
-    "android-support-v8-renderscript",
-    "android-support-multidex",
-    "android-support-multidex-instrumentation",
-]
-
 // These defaults enable doc-stub generation, api lint database generation and sdk value generation.
 stubs_defaults {
     name: "android-non-updatable-doc-stubs-defaults",
@@ -65,7 +30,6 @@
         ":android-test-mock-sources",
         ":android-test-runner-sources",
     ],
-    libs: framework_docs_only_libs,
     create_doc_stubs: true,
     write_sdk_values: true,
 }
@@ -108,7 +72,6 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args,
 }
 
 droidstubs {
@@ -117,8 +80,7 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
+    flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"],
 }
 
 droidstubs {
@@ -127,9 +89,10 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) ",
+    flags: [
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
+    ],
     generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
     write_sdk_values: false,
 }
@@ -140,10 +103,11 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\) ",
+    flags: [
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)",
+    ],
     generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
     write_sdk_values: false,
 }
@@ -152,7 +116,6 @@
     name: "framework-doc-stubs",
     defaults: ["android-non-updatable-doc-stubs-defaults"],
     srcs: [":all-modules-public-stubs-source"],
-    args: metalava_framework_docs_args,
     api_levels_module: "api_versions_public",
     aidl: {
         include_dirs: [
@@ -160,21 +123,13 @@
             "packages/modules/Media/apex/aidl/stable",
         ],
     },
-    extensions_info_file: ":sdk-extensions-info",
 }
 
 droidstubs {
     name: "framework-doc-system-stubs",
     defaults: ["framework-doc-stubs-sources-default"],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
-    api_levels_sdk_type: "system",
-    extensions_info_file: ":sdk-extensions-info",
+    flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"],
+    api_levels_module: "api_versions_system",
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -182,22 +137,11 @@
 // using droiddoc
 /////////////////////////////////////////////////////////////////////
 
-framework_docs_only_args = " -android -manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
-    "-metalavaApiSince " +
-    "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
-    "-overview $(location :frameworks-base-java-overview) " +
-    // Federate Support Library references against local API file.
-    "-federate SupportLib https://developer.android.com " +
-    "-federationapi SupportLib $(location :current-support-api) " +
-    // Federate Support Library references against local API file.
-    "-federate AndroidX https://developer.android.com " +
-    "-federationapi AndroidX $(location :current-androidx-api) "
-
 doc_defaults {
     name: "framework-docs-default",
     sdk_version: "none",
     system_modules: "none",
-    libs: framework_docs_only_libs + [
+    libs: [
         "stub-annotations",
         "unsupportedappusage",
     ],
@@ -212,6 +156,28 @@
     resourcesdir: "docs/html/reference/images/",
     resourcesoutdir: "reference/android/images/",
     lint_baseline: "javadoc-lint-baseline",
+    flags: [
+        "-android",
+        "-manifest $(location :frameworks-base-core-AndroidManifest.xml)",
+        "-metalavaApiSince",
+        "-werror",
+        "-lerror",
+        "-overview $(location :frameworks-base-java-overview)",
+        // Federate Support Library references against local API file.
+        "-federate SupportLib https://developer.android.com",
+        "-federationapi SupportLib $(location :current-support-api)",
+        // Federate Support Library references against local API file.
+        "-federate AndroidX https://developer.android.com",
+        "-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 111", // HIDDEN_SUPERCLASS
+        "-hide 113", // DEPRECATION_MISMATCH
+        "-hide 125", // REQUIRES_PERMISSION
+        "-hide 126", // BROADCAST_BEHAVIOR
+        "-hide 127", // SDK_CONSTANT
+        "-hide 128", // TODO
+    ],
     hdf: [
         "dac true",
         "sdk.codename O",
@@ -236,20 +202,6 @@
     },
 }
 
-doc_defaults {
-    name: "framework-dokka-docs-default",
-}
-
-droiddoc {
-    name: "doc-comment-check-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    args: framework_docs_only_args + " -referenceonly -parsecomments",
-    installable: false,
-}
-
 droiddoc {
     name: "offline-sdk-docs",
     defaults: ["framework-docs-default"],
@@ -261,7 +213,10 @@
     ],
     compat_config: ":global-compat-config",
     proofread_file: "offline-sdk-docs-proofread.txt",
-    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"",
+    flags: [
+        "-offlinemode",
+        "-title \"Android SDK\"",
+    ],
     static_doc_index_redirect: "docs/docs-preview-index.html",
 }
 
@@ -278,7 +233,11 @@
         "android.whichdoc offline",
     ],
     proofread_file: "offline-sdk-referenceonly-docs-proofread.txt",
-    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly",
+    flags: [
+        "-offlinemode",
+        "-title \"Android SDK\"",
+        "-referenceonly",
+    ],
     static_doc_index_redirect: "docs/docs-documentation-redirect.html",
     static_doc_properties: "docs/source.properties",
 }
@@ -296,77 +255,19 @@
         "android.whichdoc offline",
     ],
     proofread_file: "offline-system-sdk-referenceonly-docs-proofread.txt",
-    args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
-    " -offlinemode -title \"Android System SDK\" -referenceonly",
+    flags: [
+        "-hide 101",
+        "-hide 104",
+        "-hide 108",
+        "-offlinemode",
+        "-title \"Android System SDK\"",
+        "-referenceonly",
+    ],
     static_doc_index_redirect: "docs/docs-documentation-redirect.html",
     static_doc_properties: "docs/source.properties",
 }
 
 droiddoc {
-    name: "online-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-sdk-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "online-system-api-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-system-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-system-api-sdk-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -referenceonly " +
-        " -title \"Android SDK - Including system APIs.\" " +
-        " -hide 101 " +
-        " -hide 104 " +
-        " -hide 108 " +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-    installable: false,
-}
-
-droiddoc {
     name: "ds-docs-java",
     defaults: ["framework-docs-default"],
     srcs: [
@@ -377,32 +278,40 @@
         "android.hasSamples true",
     ],
     proofread_file: "ds-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -toroot / -yamlV2 -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ",
+    flags: [
+        " -toroot /",
+        "-yamlV2",
+        "-samplegroup Admin",
+        "-samplegroup Background",
+        "-samplegroup Connectivity",
+        "-samplegroup Content",
+        "-samplegroup Input",
+        "-samplegroup Media",
+        "-samplegroup Notification",
+        "-samplegroup RenderScript",
+        "-samplegroup Security",
+        "-samplegroup Sensors",
+        "-samplegroup System",
+        "-samplegroup Testing",
+        "-samplegroup UI",
+        "-samplegroup Views",
+        "-samplegroup Wearable",
+        "-devsite",
+        "-samplesdir",
+        "development/samples/browseable",
+    ],
 }
 
 droiddoc {
     name: "ds-docs-kt",
-    defaults: ["framework-dokka-docs-default"],
     srcs: [
         ":framework-doc-stubs",
     ],
-    args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
+    flags: [
+        "-noJdkLink",
+        "-links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list",
         "-noStdlibLink",
+    ],
     proofread_file: "ds-dokka-proofread.txt",
     dokka_enabled: true,
 }
@@ -455,11 +364,12 @@
     hdf: [
         "android.whichdoc online",
     ],
-    args: framework_docs_only_args +
-        " -staticonly " +
-        " -toroot / " +
-        " -devsite " +
-        " -ignoreJdLinks ",
+    flags: [
+        "-staticonly",
+        "-toroot /",
+        "-devsite",
+        "-ignoreJdLinks",
+    ],
 }
 
 droiddoc {
@@ -471,49 +381,9 @@
     hdf: [
         "android.whichdoc online",
     ],
-    args: framework_docs_only_args +
-        " -toroot / " +
-        " -atLinksNavtree " +
-        " -navtreeonly ",
-}
-
-droiddoc {
-    name: "online-sdk-dev-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
+    flags: [
+        "-toroot /",
+        "-atLinksNavtree",
+        "-navtreeonly",
     ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-sdk-dev-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "hidden-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    proofread_file: "hidden-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -referenceonly " +
-        " -title \"Android SDK - Including hidden APIs.\"",
 }
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 2d9c988..d566552 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -36,7 +36,6 @@
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args,
     check_api: {
         current: {
             api_file: ":non-updatable-current.txt",
@@ -70,19 +69,25 @@
     api_surface: "public",
 }
 
-priv_apps = " --show-annotation android.annotation.SystemApi\\(" +
-    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
-    "\\)"
+priv_apps = [
+    "--show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+        "\\)",
+]
 
-priv_apps_in_stubs = " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
-    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
-    "\\)"
+priv_apps_in_stubs = [
+    "--show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+        "\\)",
+]
 
-test = " --show-annotation android.annotation.TestApi"
+test = ["--show-annotation android.annotation.TestApi"]
 
-module_libs = " --show-annotation android.annotation.SystemApi\\(" +
-    "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
-    "\\)"
+module_libs = [
+    "--show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
+        "\\)",
+]
 
 droidstubs {
     name: "system-api-stubs-docs-non-updatable",
@@ -93,7 +98,7 @@
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args + priv_apps,
+    flags: priv_apps,
     check_api: {
         current: {
             api_file: ":non-updatable-system-current.txt",
@@ -136,7 +141,7 @@
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args + test + priv_apps_in_stubs,
+    flags: test + priv_apps_in_stubs,
     check_api: {
         current: {
             api_file: ":non-updatable-test-current.txt",
@@ -186,7 +191,7 @@
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
+    flags: priv_apps_in_stubs + module_libs,
     check_api: {
         current: {
             api_file: ":non-updatable-module-lib-current.txt",
@@ -438,6 +443,26 @@
     full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
 }
 
+// This module generates a stub jar that is a union of the test and module lib
+// non-updatable api contributions. Modules should not depend on the stub jar
+// generated from this module, as this module is strictly used for hiddenapi only.
+java_api_library {
+    name: "android-non-updatable.stubs.test_module_lib",
+    api_surface: "module_lib",
+    api_contributions: [
+        "api-stubs-docs-non-updatable.api.contribution",
+        "system-api-stubs-docs-non-updatable.api.contribution",
+        "test-api-stubs-docs-non-updatable.api.contribution",
+        "module-lib-api-stubs-docs-non-updatable.api.contribution",
+    ],
+    defaults: ["android-non-updatable_from_text_defaults"],
+    full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
+
+    // This module is only used for hiddenapi, and other modules should not
+    // depend on this module.
+    visibility: ["//visibility:private"],
+}
+
 java_defaults {
     name: "android_stubs_dists_default",
     dist: {
@@ -675,6 +700,7 @@
         "api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -690,6 +716,7 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -707,6 +734,7 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -716,12 +744,15 @@
         "android_stubs_current_contributions",
         "android_system_stubs_current_contributions",
         "android_test_frameworks_core_stubs_current_contributions",
-        "stub-annotation-defaults",
+    ],
+    libs: [
+        "stub-annotations",
     ],
     api_contributions: [
         "api-stubs-docs-non-updatable.api.contribution",
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -741,6 +772,7 @@
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -754,6 +786,32 @@
         "stub-annotations",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
+}
+
+java_api_library {
+    name: "android_test_module_lib_stubs_current.from-text",
+    api_surface: "module-lib",
+    defaults: [
+        "android_stubs_current_contributions",
+        "android_system_stubs_current_contributions",
+        "android_test_stubs_current_contributions",
+        "android_module_lib_stubs_current_contributions",
+    ],
+    libs: [
+        "android_module_lib_stubs_current_full.from-text",
+        "stub-annotations",
+    ],
+    api_contributions: [
+        "test-api-stubs-docs-non-updatable.api.contribution",
+    ],
+
+    // This module is only used to build android-non-updatable.stubs.test_module_lib
+    // and other modules should not depend on this module.
+    visibility: [
+        "//visibility:private",
+    ],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -770,6 +828,7 @@
         "android_module_lib_stubs_current.from-text",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 ////////////////////////////////////////////////////////////////////////
@@ -918,7 +977,7 @@
     merge_annotations_dirs: [
         "metalava-manual",
     ],
-    args: priv_apps,
+    flags: priv_apps,
 }
 
 java_library {
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
index 71f366a..e0153f7 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@
 
 # Convert each removed.txt to the "dex format" equivalent, and print all output.
 for f in "$@"; do
-    "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp"
+    "$metalava_path" signature-to-dex "$f" "${tmp_dir}/tmp"
     cat "${tmp_dir}/tmp"
 done
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 2cc5078..29a8dfa 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -1,68 +1,4 @@
-android/adservices/ondevicepersonalization/DownloadCompletedInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedInput [101]
-android/adservices/ondevicepersonalization/DownloadCompletedOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedOutput [101]
-android/adservices/ondevicepersonalization/EventLogRecord.java:13: lint: Unresolved link/see tag "RequestRecordRecord" in android.adservices.ondevicepersonalization.EventLogRecord [101]
-android/adservices/ondevicepersonalization/EventUrlProvider.java:43: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onEvent IsolatedWorker#onEvent" in android.adservices.ondevicepersonalization.EventUrlProvider [101]
-android/adservices/ondevicepersonalization/ExecuteInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteInput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute() OnDevicePersonalizationManager#execute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:31: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:93: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput.Builder [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onWebViewEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "WebView" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:9: lint: Unresolved link/see tag "RunTimeException" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:24: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:57: lint: Unresolved link/see tag "#onExecute()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:74: lint: Unresolved link/see tag "#onRender()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:-11: lint: Unresolved link/see tag "requestSurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedService#onExecute() IsolatedService#onExecute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:19: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView#getHostToken()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "execute" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "#execute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:64: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:69: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:70: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/RenderInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderInput [101]
-android/adservices/ondevicepersonalization/RenderInput.java:53: lint: Unresolved link/see tag "onExecute" in android.adservices.ondevicepersonalization.RenderInput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#requestSurfacePackage() OnDevicePersonalizationManager#requestSurfacePackage()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:41: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:52: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:114: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:127: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:33: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:85: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig.Builder [101]
-android/adservices/ondevicepersonalization/RequestLogRecord.java:19: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RequestLogRecord [101]
-android/adservices/ondevicepersonalization/SurfacePackageToken.java:20: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.SurfacePackageToken [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101]
-android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101]
-android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101]
-android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101]
-android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101]
-android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101]
+// b/305195721
 android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
@@ -71,6 +7,8 @@
 android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101]
+
+// b/303477132
 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101]
 android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101]
 android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101]
@@ -83,40 +21,8 @@
 android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
 android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
 android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
-android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101]
-android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101]
-android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101]
-android/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101]
-android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [101]
-android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101]
-android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101]
-android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/om/OverlayIdentifier.java:20: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayIdentifier [101]
-android/content/om/OverlayInfo.java:78: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayInfo [101]
-android/content/om/OverlayManager.java:9: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction#commit()" in android.content.om.OverlayManager [101]
-android/content/pm/PackageInstaller.java:2232: lint: Unresolved link/see tag "android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS INSTALL_GRANT_RUNTIME_PERMISSIONS" in android.content.pm.PackageInstaller.SessionParams [101]
-android/content/pm/ServiceInfo.java:176: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setDataTransfer" in android.content.pm.ServiceInfo [101]
-android/content/pm/verify/domain/DomainVerificationUserState.java:82: lint: Unresolved link/see tag "android.content.pm.verify.domain.DomainVerificationUserState.DomainState DomainState" in android.content.pm.verify.domain.DomainVerificationUserState [101]
-android/content/res/Resources.java:958: lint: Unresolved link/see tag "android.annotation.UiContext" in android.content.res.Resources [101]
-android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101]
-android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101]
-android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101]
+
+// b/303582215
 android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101]
@@ -142,162 +48,37 @@
 android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]
 android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
 android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
-android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101]
-android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101]
-android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101]
-android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANT AttributeSdkUsage#USAGE_ASSISTANT" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_GAME AttributeSdkUsage#USAGE_GAME" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_MEDIA AttributeSdkUsage#USAGE_MEDIA" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_EVENT AttributeSdkUsage#USAGE_NOTIFICATION_EVENT" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_UNKNOWN AttributeSdkUsage#USAGE_UNKNOWN" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION AttributeSdkUsage#USAGE_VOICE_COMMUNICATION" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING" in android.media.AudioAttributes.Builder [101]
-android/media/AudioFormat.java:963: lint: Unresolved link/see tag "android.media.AudioSystem#OUT_CHANNEL_COUNT_MAX AudioSystem#OUT_CHANNEL_COUNT_MAX" in android.media.AudioFormat.Builder [101]
-android/media/AudioManager.java:275: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN},     {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN},     {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},     {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG},     {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY},     {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY},     {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101]
-android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101]
-android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101]
-android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]
-android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]
-android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101]
-android/media/tv/TableResponse.java:82: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableResponse [101]
-android/net/EthernetNetworkSpecifier.java:21: lint: Unresolved link/see tag "android.net.EthernetManager" in android.net.EthernetNetworkSpecifier [101]
-android/net/eap/EapSessionConfig.java:120: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:135: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:148: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:161: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaConfig [101]
-android/net/eap/EapSessionConfig.java:390: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaPrimeConfig [101]
-android/net/eap/EapSessionConfig.java:587: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapSimConfig [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_6_GHZ WifiScanner#WIFI_BAND_6_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_UNSPECIFIED WifiScanner#WIFI_BAND_UNSPECIFIED" in android.net.wifi.MloLink [101]
-android/net/wifi/SoftApConfiguration.java:9: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder SoftApConfiguration.Builder" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:66: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setSsid(java.lang.String) Builder#setSsid(String)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:85: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setWifiSsid(android.net.wifi.WifiSsid) Builder#setWifiSsid(WifiSsid)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:96: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBssid(android.net.MacAddress) Builder#setBssid(MacAddress)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:107: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setPassphrase(java.lang.String,int) Builder#setPassphrase(String, int)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:118: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setHiddenSsid(boolean) Builder#setHiddenSsid(boolean)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:2466: lint: Unresolved link/see tag "TelephonyManager#hasCarrierPrivileges()." in android.net.wifi.WifiManager [101]
-android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig [101]
-android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig [101]
-android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101]
-android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101]
-android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
-android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
-android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101]
-android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101]
-android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101]
-android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101]
-android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101]
-android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101]
-android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101]
-android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101]
-android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101]
-android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101]
-android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101]
-android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101]
-android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101]
-android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101]
-android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101]
-android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101]
-android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101]
-android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101]
-android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101]
-android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101]
-android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101]
-android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101]
-android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101]
-android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101]
-android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101]
 
-android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
-android/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101]
-android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
+// These are javadoc errors for @ChangeId constants, which are problematic to generate documentation
+// for. They're not necessarily errors in the docs themselves but are also a limitation in the tool.
+// Regardless, the docs currently generated for them is not good, but it is also not used directly
+// in production at the moment.
+// The main limitation is that all references must be fully qualified in order to resolve properly
+// (aside from the normal limitatinos of only being able to @link public APIs).
+// See the CompatInfo.java source file in doclava for more information.
+android/net/wifi/SoftApConfiguration.java:171: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
 android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101]
-android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101]
-android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android.service.voice.AlwaysOnHotwordDetector [101]
-android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
+android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
+android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onFailure" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onUnknownFailure" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector#STATE_ERROR" in android [101]
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101]
-android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101]
-android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101]
-com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]
-
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101]
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
+com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.IdentifierType#DAB_SID_EXT" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.RadioTuner" in android [101]
+com/android/server/devicepolicy/DevicePolicyManagerService.java:861: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]
-com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34" in android [101]
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]
-com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
-com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
-
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131]
-android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131]
-android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131]
-android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-
-java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103]
+com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
+com/android/server/pm/PackageInstallerSession.java:330: lint: Unresolved link/see tag "com.android.android.server.pm#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
+com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
\ No newline at end of file
diff --git a/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
index 020ca33..ee2af12 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -16,10 +16,11 @@
 
 package com.android.commands.svc;
 
+import android.app.ActivityThread;
 import android.content.Context;
-import android.nfc.INfcAdapter;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
+import android.os.Looper;
 
 public class NfcCommand extends Svc.Command {
 
@@ -42,27 +43,26 @@
 
     @Override
     public void run(String[] args) {
-        INfcAdapter adapter = INfcAdapter.Stub.asInterface(
-                ServiceManager.getService(Context.NFC_SERVICE));
-
+        Looper.prepareMainLooper();
+        ActivityThread.initializeMainlineModules();
+        Context context = ActivityThread.systemMain().getSystemContext();
+        NfcManager nfcManager = context.getSystemService(NfcManager.class);
+        if (nfcManager == null) {
+            System.err.println("Got a null NfcManager, is the system running?");
+            return;
+        }
+        NfcAdapter adapter = nfcManager.getDefaultAdapter();
         if (adapter == null) {
             System.err.println("Got a null NfcAdapter, is the system running?");
             return;
         }
-
-        try {
-            if (args.length == 2 && "enable".equals(args[1])) {
-                adapter.enable();
-                return;
-            } else if (args.length == 2 && "disable".equals(args[1])) {
-                adapter.disable(true);
-                return;
-            }
-        } catch (RemoteException e) {
-            System.err.println("NFC operation failed: " + e);
+        if (args.length == 2 && "enable".equals(args[1])) {
+            adapter.enable();
+            return;
+        } else if (args.length == 2 && "disable".equals(args[1])) {
+            adapter.disable(true);
             return;
         }
-
         System.err.println(longHelp());
     }
 
diff --git a/cmds/svc/src/com/android/commands/svc/OWNERS b/cmds/svc/src/com/android/commands/svc/OWNERS
new file mode 100644
index 0000000..d5a5d7b
--- /dev/null
+++ b/cmds/svc/src/com/android/commands/svc/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+per-file NfcCommand.java = file:platform/packages/apps/Nfc:/OWNERS
diff --git a/config/preloaded-classes b/config/preloaded-classes
index aa34bad..7f8f5e3 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6519,12 +6519,6 @@
 android.security.attestationverification.AttestationVerificationManager
 android.security.keymaster.ExportResult$1
 android.security.keymaster.ExportResult
-android.security.keymaster.IKeyAttestationApplicationIdProvider$Stub
-android.security.keymaster.IKeyAttestationApplicationIdProvider
-android.security.keymaster.KeyAttestationApplicationId$1
-android.security.keymaster.KeyAttestationApplicationId
-android.security.keymaster.KeyAttestationPackageInfo$1
-android.security.keymaster.KeyAttestationPackageInfo
 android.security.keymaster.KeyCharacteristics$1
 android.security.keymaster.KeyCharacteristics
 android.security.keymaster.KeymasterArgument$1
@@ -6549,7 +6543,13 @@
 android.security.keystore.BackendBusyException
 android.security.keystore.DelegatingX509Certificate
 android.security.keystore.DeviceIdAttestationException
+android.security.keystore.IKeyAttestationApplicationIdProvider$Stub
+android.security.keystore.IKeyAttestationApplicationIdProvider
+android.security.keystore.KeyAttestationApplicationId$Stub
+android.security.keystore.KeyAttestationApplicationId
 android.security.keystore.KeyAttestationException
+android.security.keystore.KeyAttestationPackageInfo$Stub
+android.security.keystore.KeyAttestationPackageInfo
 android.security.keystore.KeyExpiredException
 android.security.keystore.KeyGenParameterSpec$Builder
 android.security.keystore.KeyGenParameterSpec
@@ -6572,6 +6572,8 @@
 android.security.keystore.KeystoreResponse
 android.security.keystore.ParcelableKeyGenParameterSpec$1
 android.security.keystore.ParcelableKeyGenParameterSpec
+android.security.keystore.Signature$Stub
+android.security.keystore.Signature
 android.security.keystore.SecureKeyImportUnavailableException
 android.security.keystore.StrongBoxUnavailableException
 android.security.keystore.UserAuthArgs
diff --git a/core/api/current.txt b/core/api/current.txt
index 1e310ef..18001e8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -232,6 +232,7 @@
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
+    field @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") public static final String READ_DROPBOX_DATA = "android.permission.READ_DROPBOX_DATA";
     field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
     field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
@@ -668,6 +669,7 @@
     field public static final int debuggable = 16842767; // 0x101000f
     field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
     field public static final int defaultHeight = 16844021; // 0x10104f5
+    field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
     field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
     field public static final int defaultValue = 16843245; // 0x10101ed
     field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -3286,10 +3288,10 @@
 
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
-    method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
-    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
-    method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
-    method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+    method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+    method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
+    method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public boolean clearCache();
     method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
     method public final void disableSelf();
@@ -3401,9 +3403,9 @@
     field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3
     field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9
     field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7
-    field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
-    field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
-    field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
+    field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
+    field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
+    field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
     field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
     field public static final String SERVICE_META_DATA = "android.accessibilityservice";
     field public static final int SHOW_MODE_AUTO = 0; // 0x0
@@ -5739,6 +5741,7 @@
     ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);
     method @Deprecated public void onBackStackChanged();
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
     method @Deprecated public void setActivity(android.app.Activity);
     method @Deprecated public void setMaxVisible(int);
     method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener);
@@ -6187,6 +6190,7 @@
     ctor public LocaleConfig(@NonNull android.os.LocaleList);
     method public int describeContents();
     method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
+    method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale();
     method public int getStatus();
     method @Nullable public android.os.LocaleList getSupportedLocales();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9529,7 +9533,8 @@
     method @Nullable public CharSequence getDisplayName();
     method public int getId();
     method public int getSystemDataSyncFlags();
-    method @Nullable public String getTag();
+    method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
+    method public boolean isSelfManaged();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
   }
@@ -9599,7 +9604,7 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
     method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
-    method public void clearAssociationTag(int);
+    method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method public void disableSystemDataSyncForTypes(int, int);
     method @Deprecated public void disassociate(@NonNull String);
@@ -9609,7 +9614,7 @@
     method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
     method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
     method public void requestNotificationAccess(android.content.ComponentName);
-    method public void setAssociationTag(int, @NonNull String);
+    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 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;
@@ -9640,13 +9645,13 @@
     method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
-    method @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
-    field public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
-    field public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
-    field public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
-    field public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
-    field public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
-    field public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+    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
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
 
@@ -9683,7 +9688,7 @@
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds();
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName();
     method @Nullable public String getName();
-    method @Nullable public String getPersistentDeviceId();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDevice> CREATOR;
@@ -9787,7 +9792,7 @@
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
     method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
-    method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
+    method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setPid(int);
   }
@@ -10570,7 +10575,7 @@
   public final class ContextParams {
     method @Nullable public String getAttributionTag();
     method @Nullable public android.content.AttributionSource getNextAttributionSource();
-    method @NonNull public boolean shouldRegisterAttributionSource();
+    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource();
   }
 
   public static final class ContextParams.Builder {
@@ -10579,7 +10584,7 @@
     method @NonNull public android.content.ContextParams build();
     method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
-    method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
+    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
   }
 
   public class ContextWrapper extends android.content.Context {
@@ -10992,6 +10997,7 @@
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+    field @FlaggedApi("android.content.pm.stay_stopped") public static final String ACTION_PACKAGE_UNSTOPPED = "android.intent.action.PACKAGE_UNSTOPPED";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -11096,7 +11102,7 @@
     field public static final String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
     field @Deprecated public static final String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE";
     field public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
-    field public static final String EXTRA_ARCHIVAL = "android.intent.extra.ARCHIVAL";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_ARCHIVAL = "android.intent.extra.ARCHIVAL";
     field public static final String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT";
     field public static final String EXTRA_ASSIST_INPUT_DEVICE_ID = "android.intent.extra.ASSIST_INPUT_DEVICE_ID";
     field public static final String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
@@ -11717,7 +11723,7 @@
     method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
-    method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+    method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
     method public void setTargetOverlayable(@Nullable String);
   }
 
@@ -11983,6 +11989,40 @@
     method public final int compare(android.content.pm.ApplicationInfo, android.content.pm.ApplicationInfo);
   }
 
+  @FlaggedApi("android.content.pm.archiving") public final class ArchivedActivity {
+    ctor public ArchivedActivity(@NonNull CharSequence, @NonNull android.content.ComponentName);
+    method @NonNull public android.content.ComponentName getComponentName();
+    method @Nullable public android.graphics.drawable.Drawable getIcon();
+    method @NonNull public CharSequence getLabel();
+    method @Nullable public android.graphics.drawable.Drawable getMonochromeIcon();
+    method @NonNull public android.content.pm.ArchivedActivity setComponentName(@NonNull android.content.ComponentName);
+    method @NonNull public android.content.pm.ArchivedActivity setIcon(@NonNull android.graphics.drawable.Drawable);
+    method @NonNull public android.content.pm.ArchivedActivity setLabel(@NonNull CharSequence);
+    method @NonNull public android.content.pm.ArchivedActivity setMonochromeIcon(@NonNull android.graphics.drawable.Drawable);
+  }
+
+  @FlaggedApi("android.content.pm.archiving") public final class ArchivedPackage {
+    ctor public ArchivedPackage(@NonNull String, @NonNull android.content.pm.SigningInfo, @NonNull java.util.List<android.content.pm.ArchivedActivity>);
+    method @Nullable public String getDefaultToDeviceProtectedStorage();
+    method @NonNull public java.util.List<android.content.pm.ArchivedActivity> getLauncherActivities();
+    method @NonNull public String getPackageName();
+    method @Nullable public String getRequestLegacyExternalStorage();
+    method @NonNull public android.content.pm.SigningInfo getSigningInfo();
+    method public int getTargetSdkVersion();
+    method @Nullable public String getUserDataFragile();
+    method public int getVersionCode();
+    method public int getVersionCodeMajor();
+    method @NonNull public android.content.pm.ArchivedPackage setDefaultToDeviceProtectedStorage(@NonNull String);
+    method @NonNull public android.content.pm.ArchivedPackage setLauncherActivities(@NonNull java.util.List<android.content.pm.ArchivedActivity>);
+    method @NonNull public android.content.pm.ArchivedPackage setPackageName(@NonNull String);
+    method @NonNull public android.content.pm.ArchivedPackage setRequestLegacyExternalStorage(@NonNull String);
+    method @NonNull public android.content.pm.ArchivedPackage setSigningInfo(@NonNull android.content.pm.SigningInfo);
+    method @NonNull public android.content.pm.ArchivedPackage setTargetSdkVersion(int);
+    method @NonNull public android.content.pm.ArchivedPackage setUserDataFragile(@NonNull String);
+    method @NonNull public android.content.pm.ArchivedPackage setVersionCode(int);
+    method @NonNull public android.content.pm.ArchivedPackage setVersionCodeMajor(int);
+  }
+
   public final class Attribution implements android.os.Parcelable {
     method public int describeContents();
     method @IdRes public int getLabel();
@@ -12315,6 +12355,7 @@
     method @Nullable public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int);
     method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getStagedSessions();
     method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.INSTALL_EXISTING_PACKAGES"}) public void installExistingPackage(@NonNull String, int, @Nullable android.content.IntentSender);
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void installPackageArchived(@NonNull android.content.pm.ArchivedPackage, @NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.content.IntentSender);
     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);
@@ -12596,6 +12637,7 @@
     method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @Nullable public android.content.pm.ArchivedPackage getArchivedPackage(@NonNull String);
     method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
     method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
@@ -12673,6 +12715,8 @@
     method public boolean isDeviceUpgrading();
     method public abstract boolean isInstantApp();
     method public abstract boolean isInstantApp(@NonNull String);
+    method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended();
     method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
@@ -12845,7 +12889,7 @@
     field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
     field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
     field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
-    field public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
+    field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
     field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
@@ -12910,6 +12954,7 @@
     field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
     field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
     field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+    field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L
     field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
     field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
@@ -13277,9 +13322,12 @@
 
   public final class SigningInfo implements android.os.Parcelable {
     ctor public SigningInfo();
+    ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
     ctor public SigningInfo(android.content.pm.SigningInfo);
     method public int describeContents();
     method public android.content.pm.Signature[] getApkContentsSigners();
+    method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys();
+    method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion();
     method public android.content.pm.Signature[] getSigningCertificateHistory();
     method public boolean hasMultipleSigners();
     method public boolean hasPastSigningCertificates();
@@ -13649,6 +13697,7 @@
 
   public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
     method public void close();
+    method public String getAttributeNamespace(int);
   }
 
 }
@@ -14348,14 +14397,14 @@
   public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
     method public void beginTransaction();
     method public void beginTransactionNonExclusive();
-    method public void beginTransactionReadOnly();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly();
     method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
-    method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
     method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
-    method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
     method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
     method public static boolean deleteDatabase(@NonNull java.io.File);
     method public void disableWriteAheadLogging();
@@ -14366,13 +14415,13 @@
     method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
-    method public long getLastChangedRowCount();
-    method public long getLastInsertRowId();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
-    method public long getTotalChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14594,7 +14643,7 @@
     method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
-  public final class SQLiteRawStatement implements java.io.Closeable {
+  @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
     method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
     method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
     method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -15667,7 +15716,7 @@
 
   public final class Gainmap implements android.os.Parcelable {
     ctor public Gainmap(@NonNull android.graphics.Bitmap);
-    ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
+    ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
     method public int describeContents();
     method @NonNull public float getDisplayRatioForFullHdr();
     method @NonNull public float[] getEpsilonHdr();
@@ -16086,10 +16135,12 @@
     method public String getFontFeatureSettings();
     method public float getFontMetrics(android.graphics.Paint.FontMetrics);
     method public android.graphics.Paint.FontMetrics getFontMetrics();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsForLocale(@NonNull android.graphics.Paint.FontMetrics);
     method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt);
     method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt);
     method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
     method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsIntForLocale(@NonNull android.graphics.Paint.FontMetricsInt);
     method public float getFontSpacing();
     method public String getFontVariationSettings();
     method public int getHinting();
@@ -16248,6 +16299,8 @@
 
   public static class Paint.FontMetricsInt {
     ctor public Paint.FontMetricsInt();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetricsInt);
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetrics);
     field public int ascent;
     field public int bottom;
     field public int descent;
@@ -16303,7 +16356,7 @@
     method public void arcTo(float, float, float, float, float, float, boolean);
     method public void close();
     method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
-    method public void computeBounds(@NonNull android.graphics.RectF);
+    method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
     method public void conicTo(float, float, float, float, float);
     method public void cubicTo(float, float, float, float, float, float);
     method @NonNull public android.graphics.Path.FillType getFillType();
@@ -17566,7 +17619,7 @@
     ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
     method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
     method @NonNull public android.graphics.fonts.FontFamily build();
-    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
+    method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
   }
 
   public final class FontStyle {
@@ -17663,12 +17716,14 @@
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_DISABLED = 0; // 0x0
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_ENABLED = 1; // 0x1
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
+    field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_STYLE_AUTO = 5; // 0x5
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_NO_BREAK = 4; // 0x4
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
+    field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_WORD_STYLE_AUTO = 2; // 0x2
     field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff
@@ -17754,18 +17809,18 @@
     method public float getAdvance();
     method public float getAscent();
     method public float getDescent();
-    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeBold(@IntRange(from=0) int);
-    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeItalic(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeBold(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeItalic(@IntRange(from=0) int);
     method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
     method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
     method public float getGlyphX(@IntRange(from=0) int);
     method public float getGlyphY(@IntRange(from=0) int);
-    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getItalicOverride(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getItalicOverride(@IntRange(from=0) int);
     method public float getOffsetX();
     method public float getOffsetY();
-    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getWeightOverride(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getWeightOverride(@IntRange(from=0) int);
     method @IntRange(from=0) public int glyphCount();
-    field @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public static final float NO_OVERRIDE = 1.4E-45f;
+    field @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public static final float NO_OVERRIDE = 1.4E-45f;
   }
 
   public class TextRunShaper {
@@ -18162,6 +18217,13 @@
     field public static final int YCBCR_P010 = 54; // 0x36
   }
 
+  @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
+    method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents();
+    method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean supportMixedColorSpaces();
+    method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public static final android.os.Parcelable.Creator<android.hardware.OverlayProperties> CREATOR;
+  }
+
   public final class Sensor {
     method public int getFifoMaxEventCount();
     method public int getFifoReservedEventCount();
@@ -18541,12 +18603,12 @@
     ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
     ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
-    method public javax.crypto.Cipher getCipher();
+    method @Nullable public javax.crypto.Cipher getCipher();
     method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
-    method public javax.crypto.Mac getMac();
+    method @Nullable public javax.crypto.Mac getMac();
     method @Nullable public android.security.identity.PresentationSession getPresentationSession();
-    method public java.security.Signature getSignature();
+    method @Nullable public java.security.Signature getSignature();
   }
 
 }
@@ -20399,7 +20461,7 @@
     method @Deprecated public boolean isPreviewEnabled();
     method @Deprecated public boolean isProximityCorrectionEnabled();
     method @Deprecated public boolean isShifted();
-    method public void onClick(android.view.View);
+    method @Deprecated public void onClick(android.view.View);
     method @Deprecated public void onDetachedFromWindow();
     method @Deprecated public void onDraw(android.graphics.Canvas);
     method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key);
@@ -23769,6 +23831,8 @@
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_GROUP = 2000; // 0x7d0
     field public static final int TYPE_HDMI = 9; // 0x9
+    field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_ARC = 10; // 0xa
+    field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_EARC = 29; // 0x1d
     field public static final int TYPE_HEARING_AID = 23; // 0x17
     field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
     field public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0
@@ -23968,7 +24032,7 @@
     method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
     method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.ControllerCallback);
     method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
-    method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+    method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
     method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
     method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
     method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
@@ -23977,7 +24041,7 @@
     method public void transferTo(@NonNull android.media.MediaRoute2Info);
     method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
     method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
-    method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceCallback(@NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+    method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceUpdatedCallback(@NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
     method public void unregisterTransferCallback(@NonNull android.media.MediaRouter2.TransferCallback);
   }
 
@@ -23998,11 +24062,6 @@
     method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
   }
 
-  @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public abstract static class MediaRouter2.RouteListingPreferenceCallback {
-    ctor @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public MediaRouter2.RouteListingPreferenceCallback();
-    method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void onRouteListingPreferenceChanged(@Nullable android.media.RouteListingPreference);
-  }
-
   public class MediaRouter2.RoutingController {
     method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
     method @Nullable public android.os.Bundle getControlHints();
@@ -24226,7 +24285,7 @@
 
   @Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {
     method @Deprecated public void apply();
-    method public Object clone() throws java.lang.CloneNotSupportedException;
+    method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException;
@@ -25781,15 +25840,15 @@
     method public abstract void onDisconnect(android.media.midi.MidiReceiver);
   }
 
-  public abstract class MidiUmpDeviceService extends android.app.Service {
+  @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service {
     ctor public MidiUmpDeviceService();
-    method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
-    method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
-    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public void onClose();
-    method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
-    method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
-    field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
+    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
+    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
+    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose();
+    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
+    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
+    field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
   }
 
 }
@@ -27090,6 +27149,7 @@
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public String getSelectedTrack(int);
     method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
+    method protected void onLayout(boolean, int, int, int, int);
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
     method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
     method public void reset();
@@ -28563,6 +28623,8 @@
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method public boolean isEnabled();
+    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();
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
@@ -31972,6 +32034,7 @@
     field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3
     field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2
     field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5
+    field @FlaggedApi("android.os.state_of_health_public") public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa
     field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
     field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2
     field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3
@@ -32383,7 +32446,7 @@
     method public void addData(@NonNull String, @Nullable byte[], int);
     method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException;
     method public void addText(@NonNull String, @NonNull String);
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_DROPBOX_DATA, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
     method public boolean isTagEnabled(String);
     field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED";
     field public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT";
@@ -32990,7 +33053,7 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
-    method public void setPreferPowerEfficiency(boolean);
+    method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean);
     method public void setThreads(@NonNull int[]);
     method public void updateTargetWorkDuration(long);
   }
@@ -33085,6 +33148,22 @@
     method public void onStateChanged(boolean);
   }
 
+  @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements android.os.Parcelable {
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int describeContents();
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public String getName();
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int getType();
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final android.os.Parcelable.Creator<android.os.PowerMonitor> CREATOR;
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; // 0x0
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; // 0x1
+  }
+
+  @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);
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff
+  }
+
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
@@ -33440,7 +33519,7 @@
     field public static final String DISALLOW_MICROPHONE_TOGGLE = "disallow_microphone_toggle";
     field public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
     field public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
-    field public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
+    field @FlaggedApi("android.nfc.enable_nfc_user_restriction") public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
     field public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
     field public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
     field public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -33649,6 +33728,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 public android.os.health.HealthStats takeMyUidSnapshot();
     method public android.os.health.HealthStats takeUidSnapshot(int);
     method public android.os.health.HealthStats[] takeUidSnapshots(int[]);
@@ -41557,7 +41638,7 @@
     method public android.telecom.GatewayInfo getGatewayInfo();
     method public android.net.Uri getHandle();
     method public int getHandlePresentation();
-    method @NonNull public String getId();
+    method @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") @NonNull public String getId();
     method public android.os.Bundle getIntentExtras();
     method public final int getState();
     method public android.telecom.StatusHints getStatusHints();
@@ -43090,6 +43171,7 @@
     field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
     field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool";
     field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.hide_roaming_icon") public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool";
     field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool";
     field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
     field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
@@ -43524,7 +43606,6 @@
     method public int getLongitude();
     method public int getNetworkId();
     method public int getSystemId();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
   }
 
@@ -43540,7 +43621,6 @@
     method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method @Deprecated public int getPsc();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
   }
 
@@ -43558,7 +43638,6 @@
     method @Nullable public String getMobileNetworkOperator();
     method public int getPci();
     method public int getTac();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;
   }
 
@@ -43571,7 +43650,6 @@
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
     method @IntRange(from=0, to=16777215) public int getTac();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
 
@@ -43585,7 +43663,6 @@
     method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method public int getUarfcn();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;
   }
 
@@ -43601,7 +43678,6 @@
     method @Nullable public String getMobileNetworkOperator();
     method public int getPsc();
     method public int getUarfcn();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;
   }
 
@@ -43685,6 +43761,7 @@
 
   public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getCdmaDbm();
     method public int getCdmaEcio();
@@ -43695,24 +43772,28 @@
     method public int getEvdoLevel();
     method public int getEvdoSnr();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
   }
 
   public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getBitErrorRate();
     method public int getDbm();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public int getRssi();
     method public int getTimingAdvance();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
   }
 
   public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method @IntRange(from=0, to=15) public int getCqi();
     method @IntRange(from=1, to=6) public int getCqiTableIndex();
@@ -43723,12 +43804,14 @@
     method public int getRssi();
     method public int getRssnr();
     method public int getTimingAdvance();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;
   }
 
   public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
     method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
@@ -43741,26 +43824,31 @@
     method public int getSsRsrq();
     method public int getSsSinr();
     method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
   }
 
   public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getDbm();
     method @IntRange(from=0, to=4) public int getLevel();
     method public int getRscp();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
   }
 
   public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getDbm();
     method public int getEcNo();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
   }
@@ -44216,7 +44304,7 @@
     field public static final int OUT_OF_NETWORK = 11; // 0xb
     field public static final int OUT_OF_SERVICE = 18; // 0x12
     field public static final int POWER_OFF = 17; // 0x11
-    field public static final int SATELLITE_ENABLED = 82; // 0x52
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_ENABLED = 82; // 0x52
     field public static final int SERVER_ERROR = 12; // 0xc
     field public static final int SERVER_UNREACHABLE = 9; // 0x9
     field public static final int TIMED_OUT = 13; // 0xd
@@ -44325,7 +44413,7 @@
     method public boolean isNetworkRegistered();
     method public boolean isNetworkRoaming();
     method public boolean isNetworkSearching();
-    method public boolean isNonTerrestrialNetwork();
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isNonTerrestrialNetwork();
     method @Deprecated public boolean isRegistered();
     method @Deprecated public boolean isRoaming();
     method @Deprecated public boolean isSearching();
@@ -44341,7 +44429,7 @@
     field public static final int NR_STATE_RESTRICTED = 1; // 0x1
     field public static final int SERVICE_TYPE_DATA = 2; // 0x2
     field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
-    field public static final int SERVICE_TYPE_MMS = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SERVICE_TYPE_MMS = 6; // 0x6
     field public static final int SERVICE_TYPE_SMS = 3; // 0x3
     field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
@@ -44421,7 +44509,7 @@
     method public static final boolean isStartsPostDial(char);
     method public static boolean isVoiceMailNumber(String);
     method public static boolean isWellFormedSmsAddress(String);
-    method public static boolean isWpsCallNumber(@Nullable String);
+    method @FlaggedApi("com.android.internal.telephony.flags.enable_wps_check_api_flag") public static boolean isWpsCallNumber(@NonNull String);
     method public static byte[] networkPortionToCalledPartyBCD(String);
     method public static byte[] networkPortionToCalledPartyBCDWithLength(String);
     method public static String normalizeNumber(String);
@@ -44556,7 +44644,7 @@
     method public boolean getRoaming();
     method public int getState();
     method public boolean isSearching();
-    method public boolean isUsingNonTerrestrialNetwork();
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isUsingNonTerrestrialNetwork();
     method public void setIsManualSelection(boolean);
     method public void setOperatorName(String, String, String);
     method public void setRoaming(boolean);
@@ -45342,7 +45430,7 @@
     field public static final int ERI_FLASH = 2; // 0x2
     field public static final int ERI_OFF = 1; // 0x1
     field public static final int ERI_ON = 0; // 0x0
-    field public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE";
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE";
     field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT";
     field public static final String EXTRA_APN_PROTOCOL = "android.telephony.extra.APN_PROTOCOL";
     field public static final String EXTRA_APN_TYPE = "android.telephony.extra.APN_TYPE";
@@ -45440,7 +45528,6 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
-    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
     field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
     field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
     field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
@@ -45670,6 +45757,7 @@
     field public static final int TYPE_IMS = 64; // 0x40
     field public static final int TYPE_MCX = 1024; // 0x400
     field public static final int TYPE_MMS = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
     field public static final int TYPE_SUPL = 4; // 0x4
     field public static final int TYPE_VSIM = 4096; // 0x1000
     field public static final int TYPE_XCAP = 2048; // 0x800
@@ -46548,6 +46636,7 @@
     method @Deprecated public int length();
     method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
     method @Deprecated public CharSequence subSequence(int, int);
+    method @Deprecated public String toString();
   }
 
   @Deprecated public class AndroidCharacter {
@@ -46675,10 +46764,11 @@
     method @NonNull public android.text.DynamicLayout.Builder setHyphenationFrequency(int);
     method @NonNull public android.text.DynamicLayout.Builder setIncludePad(boolean);
     method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
-    method @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.DynamicLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
     method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
-    method @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
+    method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
     method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
   }
 
@@ -46865,6 +46955,7 @@
     method public int getLineVisibleEnd(int);
     method public float getLineWidth(int);
     method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @IntRange(from=1) public final int getMaxLines();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
     method public int getOffsetForHorizontal(int, float);
     method public int getOffsetToLeftOf(int);
     method public int getOffsetToRightOf(int);
@@ -46931,6 +47022,7 @@
     method @NonNull public android.text.Layout.Builder setLineSpacingAmount(float);
     method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(@FloatRange(from=0) float);
     method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.Layout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
     method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
     method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
     method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
@@ -46999,6 +47091,7 @@
     method public void removeSpan(Object);
     method public void setSpan(Object, int, int, int);
     method public CharSequence subSequence(int, int);
+    method public String toString();
   }
 
   public static final class PrecomputedText.Params {
@@ -47128,6 +47221,7 @@
     method public void setFilters(android.text.InputFilter[]);
     method public void setSpan(Object, int, int, int);
     method public CharSequence subSequence(int, int);
+    method public String toString();
     method public static android.text.SpannableStringBuilder valueOf(CharSequence);
   }
 
@@ -47200,9 +47294,10 @@
     method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
     method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.StaticLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
     method public android.text.StaticLayout.Builder setText(CharSequence);
     method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
-    method @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
+    method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
     method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
   }
 
@@ -48768,7 +48863,9 @@
     method public boolean containsValue(Object);
     method public void ensureCapacity(int);
     method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+    method public boolean equals(@Nullable Object);
     method public V get(Object);
+    method public int hashCode();
     method public int indexOfKey(Object);
     method public int indexOfValue(Object);
     method public boolean isEmpty();
@@ -48800,7 +48897,9 @@
     method public boolean contains(Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void ensureCapacity(int);
+    method public boolean equals(@Nullable Object);
     method public void forEach(java.util.function.Consumer<? super E>);
+    method public int hashCode();
     method public int indexOf(Object);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
@@ -49805,6 +49904,7 @@
     method public android.view.Display.Mode getMode();
     method public String getName();
     method @Deprecated public int getOrientation();
+    method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public android.hardware.OverlayProperties getOverlaySupport();
     method @Deprecated public int getPixelFormat();
     method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
     method public long getPresentationDeadlineNanos();
@@ -50083,13 +50183,6 @@
     field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
   }
 
-  @FlaggedApi("android.view.flags.scroll_feedback_api") public class HapticScrollFeedbackProvider implements android.view.ScrollFeedbackProvider {
-    ctor public HapticScrollFeedbackProvider(@NonNull android.view.View);
-    method public void onScrollLimit(int, int, int, boolean);
-    method public void onScrollProgress(int, int, int, int);
-    method public void onSnapToItem(int, int, int);
-  }
-
   public class InflateException extends java.lang.RuntimeException {
     ctor public InflateException();
     ctor public InflateException(String, Throwable);
@@ -51256,9 +51349,10 @@
   }
 
   @FlaggedApi("android.view.flags.scroll_feedback_api") public interface ScrollFeedbackProvider {
-    method public void onScrollLimit(int, int, int, boolean);
-    method public void onScrollProgress(int, int, int, int);
-    method public void onSnapToItem(int, int, int);
+    method @FlaggedApi("android.view.flags.scroll_feedback_api") @NonNull public static android.view.ScrollFeedbackProvider createProvider(@NonNull android.view.View);
+    method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollLimit(int, int, int, boolean);
+    method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollProgress(int, int, int, int);
+    method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onSnapToItem(int, int, int);
   }
 
   public class SearchEvent {
@@ -52540,7 +52634,6 @@
     method @Deprecated public static int getEdgeSlop();
     method @Deprecated public static int getFadingEdgeLength();
     method @Deprecated public static long getGlobalActionKeyTimeout();
-    method @FlaggedApi("android.view.flags.scroll_feedback_api") public int getHapticScrollFeedbackTickInterval(int, int, int);
     method public static int getJumpTapTimeout();
     method public static int getKeyRepeatDelay();
     method public static int getKeyRepeatTimeout();
@@ -52580,7 +52673,6 @@
     method @Deprecated public static int getWindowTouchSlop();
     method public static long getZoomControlsTimeout();
     method public boolean hasPermanentMenuKey();
-    method @FlaggedApi("android.view.flags.scroll_feedback_api") public boolean isHapticScrollFeedbackEnabled(int, int, int);
     method public boolean shouldShowMenuShortcutsWhenKeyboardPresent();
   }
 
@@ -53122,6 +53214,7 @@
     method @Nullable public abstract android.view.View getCurrentFocus();
     method @NonNull public abstract android.view.View getDecorView();
     method public static int getDefaultFeatures(android.content.Context);
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
     method public android.transition.Transition getEnterTransition();
     method public android.transition.Transition getExitTransition();
     method protected final int getFeatures();
@@ -53191,6 +53284,7 @@
     method public abstract void setDecorCaptionShade(int);
     method public void setDecorFitsSystemWindows(boolean);
     method protected void setDefaultWindowFormat(int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
     method public void setDimAmount(float);
     method public void setElevation(float);
     method public void setEnterTransition(android.transition.Transition);
@@ -53540,6 +53634,7 @@
     method public int describeContents();
     method public int getBlurBehindRadius();
     method public int getColorMode();
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
     method public int getFitInsetsSides();
     method public int getFitInsetsTypes();
     method public final CharSequence getTitle();
@@ -53549,6 +53644,7 @@
     method public void setBlurBehindRadius(@IntRange(from=0) int);
     method public void setCanPlayMoveAnimation(boolean);
     method public void setColorMode(int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float);
     method public void setFitInsetsIgnoringVisibility(boolean);
     method public void setFitInsetsSides(int);
     method public void setFitInsetsTypes(int);
@@ -54469,8 +54565,8 @@
   public class AnimationUtils {
     ctor public AnimationUtils();
     method public static long currentAnimationTimeMillis();
-    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
-    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
+    method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeMillis();
+    method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeNanos();
     method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
     method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
     method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -54659,7 +54755,6 @@
 
   public final class AutofillManager {
     method public void cancel();
-    method public void clearAutofillRequestCallback();
     method public void commit();
     method public void disableAutofillServices();
     method @Nullable public android.content.ComponentName getAutofillServiceComponentName();
@@ -54686,7 +54781,6 @@
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
-    method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     method public void setUserData(@Nullable android.service.autofill.UserData);
     method public boolean showAutofillDialog(@NonNull android.view.View);
     method public boolean showAutofillDialog(@NonNull android.view.View, int);
@@ -54707,10 +54801,6 @@
     field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3
   }
 
-  public interface AutofillRequestCallback {
-    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
-  }
-
   public final class AutofillValue implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutofillValue forDate(long);
@@ -55162,12 +55252,10 @@
     ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
   }
 
@@ -57543,6 +57631,7 @@
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int);
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
   }
 
   @Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
@@ -57621,6 +57710,7 @@
     method public long getSelectedItemId();
     method public int getSelectedItemPosition();
     method public abstract android.view.View getSelectedView();
+    method protected void onLayout(boolean, int, int, int, int);
     method public boolean performItemClick(android.view.View, int, long);
     method public abstract void setAdapter(T);
     method public void setEmptyView(android.view.View);
@@ -58267,6 +58357,7 @@
     method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();
     method public boolean getMeasureAllChildren();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setMeasureAllChildren(boolean);
   }
 
@@ -58320,6 +58411,7 @@
     method public boolean getUseDefaultMargins();
     method public boolean isColumnOrderPreserved();
     method public boolean isRowOrderPreserved();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setAlignmentMode(int);
     method public void setColumnCount(int);
     method public void setColumnOrderPreserved(boolean);
@@ -58542,6 +58634,7 @@
     method public float getWeightSum();
     method public boolean isBaselineAligned();
     method public boolean isMeasureWithLargestChildEnabled();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setBaselineAligned(boolean);
     method public void setBaselineAlignedChildIndex(int);
     method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -59090,6 +59183,7 @@
     method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method public int getGravity();
     method public int getIgnoreGravity();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setGravity(int);
     method public void setHorizontalGravity(int);
     method public void setIgnoreGravity(int);
@@ -59544,6 +59638,7 @@
     method @Deprecated public boolean isMoving();
     method @Deprecated public boolean isOpened();
     method @Deprecated public void lock();
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
     method @Deprecated public void open();
     method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);
     method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener);
@@ -59878,6 +59973,7 @@
     method public int getMinHeight();
     method public int getMinLines();
     method public int getMinWidth();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
     method public final android.text.method.MovementMethod getMovementMethod();
     method public int getOffsetForPosition(float, float);
     method public android.text.TextPaint getPaint();
@@ -60014,6 +60110,7 @@
     method public void setMinHeight(int);
     method public void setMinLines(int);
     method public void setMinWidth(int);
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
     method public final void setMovementMethod(android.text.method.MovementMethod);
     method public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener);
     method public void setPaintFlags(int);
@@ -60190,6 +60287,7 @@
     method public boolean hideOverflowMenu();
     method public void inflateMenu(@MenuRes int);
     method public boolean isOverflowMenuShowing();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setCollapseContentDescription(@StringRes int);
     method public void setCollapseContentDescription(@Nullable CharSequence);
     method public void setCollapseIcon(@DrawableRes int);
@@ -60344,7 +60442,7 @@
     method @Deprecated public android.view.View getZoomControls();
     method @Deprecated public boolean isAutoDismissed();
     method @Deprecated public boolean isVisible();
-    method public boolean onTouch(android.view.View, android.view.MotionEvent);
+    method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);
     method @Deprecated public void setAutoDismissed(boolean);
     method @Deprecated public void setFocusable(boolean);
     method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 052d614..5bbff0b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,9 +6,7 @@
     field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
     field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
     field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
-    field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
-    field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
-    field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
+    field @FlaggedApi("android.companion.flags.companion_transport_apis") public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
   }
 
 }
@@ -87,21 +85,21 @@
 package android.companion {
 
   public final class CompanionDeviceManager {
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
-    field public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
-    field public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
-    field public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
   }
 
-  public static interface CompanionDeviceManager.OnMessageReceivedListener {
+  @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnMessageReceivedListener {
     method public void onMessageReceived(int, @NonNull byte[]);
   }
 
-  public static interface CompanionDeviceManager.OnTransportsChangedListener {
+  @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnTransportsChangedListener {
     method public void onTransportsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
   }
 
@@ -122,7 +120,6 @@
     method @NonNull public android.os.IBinder getProcessToken();
     method @NonNull public android.os.UserHandle getUser();
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
-    field public static final String REMOTE_AUTH_SERVICE = "remote_auth";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a99eeb0..119e0ad 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -297,6 +297,8 @@
     field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
     field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
     field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+    field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
+    field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
     field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
     field public static final String RECOVERY = "android.permission.RECOVERY";
@@ -310,9 +312,10 @@
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
-    field public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+    field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
     field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
+    field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final String RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT = "android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
     field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM";
     field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
@@ -652,7 +655,6 @@
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
     field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
-    field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio";
     field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
     field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
     field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
@@ -2450,6 +2452,7 @@
     method public int getFeatureType();
     method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction();
     method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid();
+    method @FlaggedApi("android.app.smartspace.flags.remote_views") @Nullable public android.widget.RemoteViews getRemoteViews();
     method public float getScore();
     method @Nullable public android.net.Uri getSliceUri();
     method @NonNull public String getSmartspaceTargetId();
@@ -2524,6 +2527,7 @@
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(int);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+    method @FlaggedApi("android.app.smartspace.flags.remote_views") @NonNull public android.app.smartspace.SmartspaceTarget.Builder setRemoteViews(@NonNull android.widget.RemoteViews);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(float);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(boolean);
     method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(boolean);
@@ -3148,7 +3152,6 @@
 
   public final class AssociationInfo implements android.os.Parcelable {
     method @NonNull public String getPackageName();
-    method public boolean isSelfManaged();
   }
 
   public final class CompanionDeviceManager {
@@ -3209,7 +3212,7 @@
     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();
-    method @Nullable public String getPersistentDeviceId();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
     method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
@@ -3486,7 +3489,7 @@
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
     field public static final String TETHERING_SERVICE = "tethering";
-    field public static final String THREAD_NETWORK_SERVICE = "thread_network";
+    field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_SERVICE = "thread_network";
     field public static final String TIME_MANAGER_SERVICE = "time_manager";
     field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
@@ -3554,7 +3557,7 @@
     field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
     field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
     field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
-    field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+    field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -4027,7 +4030,7 @@
     field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
-    field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
+    field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
     field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -4058,7 +4061,9 @@
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+    method public int describeContents();
     method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
   }
 
@@ -4529,7 +4534,7 @@
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
-    field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
+    field @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
     field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000
     field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
@@ -5581,7 +5586,8 @@
     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 @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @Deprecated @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);
     method public void removeOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
@@ -5635,12 +5641,13 @@
     field @Deprecated 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
     field public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; // 0x2714
     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 public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
-    field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+    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_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
@@ -5655,6 +5662,14 @@
     field @Deprecated public static final int PROGRAM_TYPE_SXM = 7; // 0x7
     field @Deprecated public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_1 = 1; // 0x1
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_2 = 2; // 0x2
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_3 = 4; // 0x4
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_4 = 8; // 0x8
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_5 = 16; // 0x10
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_6 = 32; // 0x20
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_7 = 64; // 0x40
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_8 = 128; // 0x80
   }
 
   public static final class ProgramSelector.Identifier implements android.os.Parcelable {
@@ -5667,7 +5682,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
   }
 
-  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
+  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
   }
 
   @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
@@ -5691,7 +5706,9 @@
     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 public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field @Deprecated 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
     field public static final int CONFIG_FORCE_MONO = 1; // 0x1
     field public static final int CONFIG_RDS_AF = 4; // 0x4
@@ -5819,8 +5836,11 @@
     method @Deprecated public int getSubChannel();
     method @NonNull public java.util.Map<java.lang.String,java.lang.String> getVendorInfo();
     method @Deprecated public boolean isDigital();
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdAudioAvailable();
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdSisAvailable();
     method public boolean isLive();
     method public boolean isMuted();
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isSignalAcquired();
     method public boolean isStereo();
     method public boolean isTrafficAnnouncementActive();
     method public boolean isTrafficProgram();
@@ -5836,6 +5856,7 @@
     method public android.hardware.radio.RadioMetadata.Clock getClock(String);
     method public int getInt(String);
     method public String getString(String);
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public String[] getStringArray(@NonNull String);
     method public java.util.Set<java.lang.String> keySet();
     method public int size();
     method public void writeToParcel(android.os.Parcel, int);
@@ -5844,6 +5865,9 @@
     field public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
     field public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
     field public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMERCIAL = "android.hardware.radio.metadata.COMMERCIAL";
     field public static final String METADATA_KEY_DAB_COMPONENT_NAME = "android.hardware.radio.metadata.DAB_COMPONENT_NAME";
     field public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
     field public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME";
@@ -5851,6 +5875,9 @@
     field public static final String METADATA_KEY_DAB_SERVICE_NAME = "android.hardware.radio.metadata.DAB_SERVICE_NAME";
     field public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT";
     field public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_LONG = "android.hardware.radio.metadata.HD_STATION_NAME_LONG";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_SHORT = "android.hardware.radio.metadata.HD_STATION_NAME_SHORT";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE";
     field public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
     field public static final String METADATA_KEY_PROGRAM_NAME = "android.hardware.radio.metadata.PROGRAM_NAME";
     field public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
@@ -5859,6 +5886,7 @@
     field public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
     field public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
     field public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS";
   }
 
   public static final class RadioMetadata.Builder {
@@ -5869,6 +5897,7 @@
     method public android.hardware.radio.RadioMetadata.Builder putClock(String, long, int);
     method public android.hardware.radio.RadioMetadata.Builder putInt(String, int);
     method public android.hardware.radio.RadioMetadata.Builder putString(String, String);
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public android.hardware.radio.RadioMetadata.Builder putStringArray(@NonNull String, @NonNull String[]);
   }
 
   public static final class RadioMetadata.Clock implements android.os.Parcelable {
@@ -5958,6 +5987,7 @@
 
   public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
     ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+    method public int describeContents();
     method public int getId();
     method @NonNull public java.util.Locale getLocale();
     method public int getRecognitionModes();
@@ -5980,6 +6010,7 @@
   public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+    method public int describeContents();
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();
     method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5987,6 +6018,7 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+    method public int describeContents();
     method public int getEnd();
     method public int getStart();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -6714,7 +6746,7 @@
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public String toLogFriendlyString();
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
+    method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
     field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
@@ -6797,6 +6829,7 @@
 
   public abstract class MusicRecognitionService extends android.app.Service {
     ctor public MusicRecognitionService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
   }
 
@@ -6855,6 +6888,7 @@
 
   public abstract class SoundTriggerDetectionService extends android.app.Service {
     ctor public SoundTriggerDetectionService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
     method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
     method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int);
@@ -7568,6 +7602,7 @@
   }
 
   public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method protected void finalize();
     method public long getAudioHandle();
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public long getAvDataId();
@@ -8962,6 +8997,8 @@
 package android.net.metrics {
 
   @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class ApfProgramEvent.Builder {
@@ -8976,6 +9013,8 @@
   }
 
   @Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class ApfStats.Builder {
@@ -8994,6 +9033,8 @@
   }
 
   @Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class DhcpClientEvent.Builder {
@@ -9005,7 +9046,9 @@
 
   @Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public DhcpErrorEvent(int);
+    method @Deprecated public int describeContents();
     method @Deprecated public static int errorCodeWithOption(int, int);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
     field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
     field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
@@ -9043,6 +9086,8 @@
 
   @Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public IpManagerEvent(int, long);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
     field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
     field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
@@ -9055,6 +9100,8 @@
 
   @Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public IpReachabilityEvent(int);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int NUD_FAILED = 512; // 0x200
     field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
     field @Deprecated public static final int PROBE = 256; // 0x100
@@ -9065,6 +9112,8 @@
   @Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public NetworkEvent(int, long);
     ctor @Deprecated public NetworkEvent(int);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
     field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
     field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
@@ -9081,6 +9130,8 @@
   }
 
   @Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class RaEvent.Builder {
@@ -9095,7 +9146,9 @@
   }
 
   @Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
     method @Deprecated @NonNull public static String getProbeName(int);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
     field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
     field @Deprecated public static final int PROBE_DNS = 0; // 0x0
@@ -9511,7 +9564,7 @@
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
-    method public boolean isBatteryCharging();
+    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
     field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -9525,7 +9578,7 @@
   public static final class NetworkProviderInfo.Builder {
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -9605,6 +9658,7 @@
     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 @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();
@@ -9613,6 +9667,7 @@
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     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);
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
@@ -9704,7 +9759,6 @@
     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 @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa
     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
@@ -9772,8 +9826,8 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int);
@@ -10455,7 +10509,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
-    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
+    method public boolean isUserVisible();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
@@ -11500,6 +11554,7 @@
 
   public abstract class FieldClassificationService extends android.app.Service {
     ctor public FieldClassificationService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
     method public void onConnected();
     method public void onDisconnected();
@@ -11578,6 +11633,7 @@
     method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
     method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -11616,6 +11672,7 @@
 
   public final class FillWindow implements java.lang.AutoCloseable {
     ctor public FillWindow();
+    method public void close();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
   }
@@ -11641,6 +11698,7 @@
   public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {
     ctor public CarrierMessagingServiceWrapper();
     method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
+    method public void close();
     method public void disconnect();
     method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
     method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
@@ -11691,6 +11749,7 @@
     method public final void disableSelf();
     method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);
     method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
     method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
@@ -11729,6 +11788,7 @@
 
   public abstract class ContentSuggestionsService extends android.app.Service {
     ctor public ContentSuggestionsService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
     method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);
     method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle);
@@ -11742,6 +11802,7 @@
 
   public abstract class DataLoaderService extends android.app.Service {
     ctor public DataLoaderService();
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
   }
 
@@ -12414,6 +12475,7 @@
 
   public class TraceReportService extends android.app.Service {
     ctor public TraceReportService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
   }
 
@@ -12653,6 +12715,7 @@
   public static final class HotwordDetectionService.Callback {
     method public void onDetected(@NonNull android.service.voice.HotwordDetectedResult);
     method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
+    method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public void onTrainingData(@NonNull android.service.voice.HotwordTrainingData);
   }
 
   public final class HotwordDetectionServiceFailure implements android.os.Parcelable {
@@ -12668,6 +12731,8 @@
     field public static final int ERROR_CODE_DETECT_TIMEOUT = 4; // 0x4
     field public static final int ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION = 5; // 0x5
     field public static final int ERROR_CODE_ON_DETECTED_STREAM_COPY_FAILURE = 6; // 0x6
+    field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED = 8; // 0x8
+    field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9; // 0x9
     field public static final int ERROR_CODE_REMOTE_EXCEPTION = 7; // 0x7
     field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
   }
@@ -12689,6 +12754,7 @@
     method public void onRecognitionPaused();
     method public void onRecognitionResumed();
     method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
+    method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public default void onTrainingData(@NonNull android.service.voice.HotwordTrainingData);
     method public default void onUnknownFailure(@NonNull String);
   }
 
@@ -12709,7 +12775,7 @@
     method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
   }
 
-  public final class HotwordTrainingAudio implements android.os.Parcelable {
+  @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingAudio implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.media.AudioFormat getAudioFormat();
     method @NonNull public int getAudioType();
@@ -12720,7 +12786,7 @@
     field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
   }
 
-  public static final class HotwordTrainingAudio.Builder {
+  @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingAudio.Builder {
     ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat);
     method @NonNull public android.service.voice.HotwordTrainingAudio build();
     method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
@@ -12729,7 +12795,7 @@
     method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
   }
 
-  public final class HotwordTrainingData implements android.os.Parcelable {
+  @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingData implements android.os.Parcelable {
     method public int describeContents();
     method public static int getMaxTrainingDataBytes();
     method public int getTimeoutStage();
@@ -12738,7 +12804,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
   }
 
-  public static final class HotwordTrainingData.Builder {
+  @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingData.Builder {
     ctor public HotwordTrainingData.Builder();
     method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
     method @NonNull public android.service.voice.HotwordTrainingData build();
@@ -13039,6 +13105,7 @@
     method @Nullable public android.content.ComponentName getCallScreeningComponent();
     method public boolean isBlocked();
     method public boolean isInContacts();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;
   }
 
@@ -13228,7 +13295,7 @@
     method public void requestStreamingState(int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
-    field public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
+    field @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
     field public static final int STATE_DISCONNECTED = 3; // 0x3
     field public static final int STATE_HOLDING = 2; // 0x2
     field public static final int STATE_STREAMING = 1; // 0x1
@@ -13348,6 +13415,7 @@
     method public int getReason();
     method public int getTimeoutSeconds();
     method public boolean isEnabled();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
     field public static final int REASON_ALL = 4; // 0x4
     field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
@@ -13632,6 +13700,7 @@
     method public int getDownlinkCapacityKbps();
     method public int getType();
     method public int getUplinkCapacityKbps();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
     field public static final int INVALID = -1; // 0xffffffff
     field public static final int LCE_TYPE_COMBINED = 2; // 0x2
@@ -13641,8 +13710,10 @@
 
   public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public LteVopsSupportInfo(int, int);
+    method public boolean equals(@Nullable Object);
     method public int getEmcBearerSupport();
     method public int getVopsSupport();
+    method public int hashCode();
     method public boolean isEmergencyServiceFallbackSupported();
     method public boolean isEmergencyServiceSupported();
     method public boolean isVopsSupported();
@@ -13709,7 +13780,7 @@
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean);
-    method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int);
@@ -13743,9 +13814,11 @@
 
   public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public NrVopsSupportInfo(int, int, int);
+    method public boolean equals(@Nullable Object);
     method public int getEmcSupport();
     method public int getEmfSupport();
     method public int getVopsSupport();
+    method public int hashCode();
     method public boolean isEmergencyServiceFallbackSupported();
     method public boolean isEmergencyServiceSupported();
     method public boolean isVopsSupported();
@@ -14675,6 +14748,7 @@
     field public static final String TYPE_IMS_STRING = "ims";
     field public static final String TYPE_MCX_STRING = "mcx";
     field public static final String TYPE_MMS_STRING = "mms";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
     field public static final String TYPE_SUPL_STRING = "supl";
     field public static final String TYPE_VSIM_STRING = "vsim";
     field public static final String TYPE_XCAP_STRING = "xcap";
@@ -14858,6 +14932,7 @@
 
   public abstract class QualifiedNetworksService extends android.app.Service {
     ctor public QualifiedNetworksService();
+    method public android.os.IBinder onBind(android.content.Intent);
     method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
     field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
   }
@@ -15049,6 +15124,7 @@
   public class GbaService extends android.app.Service {
     ctor public GbaService();
     method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean);
+    method public android.os.IBinder onBind(android.content.Intent);
     method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;
     method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;
     field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService";
@@ -15551,6 +15627,7 @@
     method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
     method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);
     method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
+    method public android.os.IBinder onBind(android.content.Intent);
     method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;
     method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();
     method public void readyForFeatureCreation();
@@ -16679,157 +16756,177 @@
 
 package android.telephony.satellite {
 
-  public final class AntennaDirection implements android.os.Parcelable {
-    method public int describeContents();
-    method public float getX();
-    method public float getY();
-    method public float getZ();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaDirection 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 getX();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getY();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getZ();
+    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.AntennaDirection> CREATOR;
   }
 
-  public final class AntennaPosition implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection();
-    method public int getSuggestedHoldPosition();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaPosition 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") @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getSuggestedHoldPosition();
+    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.AntennaPosition> CREATOR;
   }
 
-  public final class PointingInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method public float getSatelliteAzimuthDegrees();
-    method public float getSatelliteElevationDegrees();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
+    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
+    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 int getLevel();
+    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.NtnSignalStrength> CREATOR;
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1
   }
 
-  public final class SatelliteCapabilities implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap();
-    method public int getMaxBytesPerOutgoingDatagram();
-    method @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies();
-    method public boolean isPointingRequired();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
   }
 
-  public final class SatelliteDatagram implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public byte[] getSatelliteDatagram();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR;
+  @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") 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;
   }
 
-  public interface SatelliteDatagramCallback {
-    method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities 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") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getMaxBytesPerOutgoingDatagram();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isPointingRequired();
+    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.SatelliteCapabilities> CREATOR;
   }
 
-  public final class SatelliteManager {
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram 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") @NonNull public byte[] getSatelliteDatagram();
+    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.SatelliteDatagram> CREATOR;
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteDatagramCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
+  }
+
+  @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 @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.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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
-    method @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.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 int 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 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 @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.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 @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 @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 @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 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 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") @NonNull @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 @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 @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 @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 @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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
-    method @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 @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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
-    field public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
-    field public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
-    field public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2
-    field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3
-    field public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1
-    field public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0
-    field public static final int DISPLAY_MODE_CLOSED = 3; // 0x3
-    field public static final int DISPLAY_MODE_FIXED = 1; // 0x1
-    field public static final int DISPLAY_MODE_OPENED = 2; // 0x2
-    field public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
-    field public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
-    field public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
-    field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
-    field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
-    field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+    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 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 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 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);
+    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
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_CLOSED = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_FIXED = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_OPENED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
+    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_GEOLOCATION = 1; // 0x1
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
+    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
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
-    field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
-    field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
-    field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
-    field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
-    field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
-    field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
-    field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
-    field public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
-    field public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
-    field public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
-    field public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
-    field public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
-    field public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
-    field public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
-    field public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
-    field public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
-    field public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
-    field public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
-    field public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
-    field public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
-    field public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
-    field public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
-    field public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
-    field public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15
-    field public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb
-    field public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2
-    field public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3
-    field public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd
-    field public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe
-    field public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
+    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_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
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0
   }
 
-  public static class SatelliteManager.SatelliteException extends java.lang.Exception {
-    ctor public SatelliteManager.SatelliteException(int);
-    method public int getErrorCode();
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static class SatelliteManager.SatelliteException extends java.lang.Exception {
+    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public SatelliteManager.SatelliteException(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode();
   }
 
-  public interface SatelliteProvisionStateCallback {
-    method public void onSatelliteProvisionStateChanged(boolean);
+  @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);
   }
 
-  public interface SatelliteStateCallback {
-    method 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);
   }
 
-  public interface SatelliteTransmissionUpdateCallback {
-    method public void onReceiveDatagramStateChanged(int, int, int);
-    method public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo);
-    method public void onSendDatagramStateChanged(int, int, int);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int);
   }
 
 }
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6b..a501031 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@
     New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int)
 UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>):
     New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>)
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging():
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean):
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean)
 UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
     New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
 UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 07af8d6..83b6880 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -28,7 +28,6 @@
     field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
     field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
-    field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
     field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
     field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE";
@@ -56,7 +55,6 @@
     field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
-    field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
     field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
@@ -543,7 +541,7 @@
     field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
     field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
     field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
-    field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+    field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
   }
 
   public class DevicePolicyManager {
@@ -840,7 +838,7 @@
 
 package android.companion {
 
-  public static final class AssociationInfo.Builder {
+  @FlaggedApi("android.companion.new_association_builder") public static final class AssociationInfo.Builder {
     ctor public AssociationInfo.Builder(int, int, @NonNull String);
     ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo);
     method @NonNull public android.companion.AssociationInfo build();
@@ -853,13 +851,13 @@
     method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
     method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
     method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
-    method @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
+    method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
     method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
   }
 
   public final class CompanionDeviceManager {
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
-    field public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
@@ -874,7 +872,7 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
-    ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
+    ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
     ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
@@ -1181,6 +1179,7 @@
     method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
     method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
     method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+    method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity();
     method @Nullable public CharSequence getSettingsSubtitle();
     method @NonNull public boolean hasCapability(@NonNull String);
     method public boolean isEnabled();
@@ -2118,7 +2117,7 @@
 
   public class SharedConnectivityManager {
     method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
-    method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+    method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
     method @Nullable public android.content.ServiceConnection getServiceConnection();
     method public void setService(@Nullable android.os.IInterface);
   }
@@ -2149,7 +2148,9 @@
   }
 
   public final class BugreportParams {
-    field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+    field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4
+    field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+    field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7
   }
 
   public class Build {
@@ -2288,7 +2289,7 @@
   public final class PowerManager {
     method public boolean areAutoPowerSaveModesEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
-    method public boolean isBatterySaverSupported();
+    method @FlaggedApi("android.os.battery_saver_supported_check_api") public boolean isBatterySaverSupported();
     field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
     field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
   }
@@ -2958,10 +2959,6 @@
     method @Deprecated public boolean isBound();
   }
 
-  public class NotificationRankingUpdate implements android.os.Parcelable {
-    method public final boolean isFdNotNullAndClosed();
-  }
-
 }
 
 package android.service.quickaccesswallet {
@@ -3053,6 +3050,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+    method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT) public final void resetHotwordTrainingDataEgressCountForTest();
     method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
   }
 
@@ -3191,7 +3189,6 @@
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
     field public static final int HAL_SERVICE_MODEM = 3; // 0x3
     field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
-    field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
     field public static final int HAL_SERVICE_SIM = 5; // 0x5
     field public static final int HAL_SERVICE_VOICE = 6; // 0x6
     field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3284,12 +3281,12 @@
   }
 
   public class MeasuredParagraph {
-    method @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback);
   }
 
-  public static interface MeasuredParagraph.StyleRunCallback {
-    method public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
-    method public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean);
+  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static interface MeasuredParagraph.StyleRunCallback {
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean);
   }
 
   public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
@@ -3411,7 +3408,7 @@
 
   public final class Choreographer {
     method public static long getFrameDelay();
-    method public long getFrameTimeNanos();
+    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public long getFrameTimeNanos();
     method public void postCallback(int, Runnable, Object);
     method public void postCallbackDelayed(int, Runnable, Object, long);
     method public void removeCallbacks(int, Runnable, Object);
@@ -3497,10 +3494,6 @@
     method public boolean isSystemGroup();
   }
 
-  public abstract class LayoutInflater {
-    method public void setPrecompiledLayoutsEnabledForTesting(boolean);
-  }
-
   public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable {
     method public int getDisplayId();
     method public void setActionButton(int);
@@ -3572,8 +3565,8 @@
     method public default void holdLock(android.os.IBinder, int);
     method public default boolean isGlobalKey(int);
     method public default boolean isTaskSnapshotSupported();
-    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl);
+    method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window);
+    method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl);
     method public default void setDisplayImePolicy(int, int);
     method public default void setShouldShowSystemDecors(int, boolean);
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -3591,8 +3584,8 @@
     field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
     field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
     field public CharSequence accessibilityTitle;
-    field public float preferredMaxDisplayRefreshRate;
-    field public float preferredMinDisplayRefreshRate;
+    field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate;
+    field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate;
     field public int privateFlags;
   }
 
@@ -3622,7 +3615,6 @@
 
   public final class AccessibilityWindowInfo implements android.os.Parcelable {
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
-    field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff
   }
 
 }
@@ -3630,7 +3622,7 @@
 package android.view.animation {
 
   public class AnimationUtils {
-    method public static void lockAnimationClock(long, long);
+    method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static void lockAnimationClock(long, long);
     method public static void unlockAnimationClock();
   }
 
@@ -3805,14 +3797,14 @@
   public final class InputMethodManager {
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
     method public int getDisplayId();
-    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
-    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
+    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
+    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
-    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
+    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
@@ -3950,7 +3942,9 @@
 package android.window {
 
   public final class BackNavigationInfo implements android.os.Parcelable {
+    method public int describeContents();
     method @NonNull public static String typeToString(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
     field public static final String KEY_TRIGGER_BACK = "TriggerBack";
     field public static final int TYPE_CALLBACK = 4; // 0x4
@@ -4011,11 +4005,13 @@
   }
 
   public final class TaskFragmentCreationParams implements android.os.Parcelable {
+    method public int describeContents();
     method @NonNull public android.os.IBinder getFragmentToken();
     method @NonNull public android.graphics.Rect getInitialRelativeBounds();
     method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
     method @NonNull public android.os.IBinder getOwnerToken();
     method public int getWindowingMode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
   }
 
@@ -4027,6 +4023,7 @@
   }
 
   public final class TaskFragmentInfo implements android.os.Parcelable {
+    method public int describeContents();
     method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
     method @NonNull public java.util.List<android.os.IBinder> getActivities();
     method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment();
@@ -4040,6 +4037,7 @@
     method public boolean isEmpty();
     method public boolean isTaskClearedForReuse();
     method public boolean isVisible();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
   }
 
@@ -4062,6 +4060,8 @@
   }
 
   public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
   }
 
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5..105e764 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@
     New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
 UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
     New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver():
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver()
 UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
     New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
 UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE:
diff --git a/core/java/Android.bp b/core/java/Android.bp
index c3f3d87..ddb221f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@
     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: [
@@ -23,11 +32,6 @@
     visibility: ["//frameworks/base"],
 }
 
-filegroup {
-    name: "IKeyAttestationApplicationIdProvider.aidl",
-    srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
-}
-
 aidl_library {
     name: "IDropBoxManagerService_aidl",
     srcs: [
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3370c12..1000612 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -23,6 +23,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
 import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -793,6 +794,7 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
+    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
     @IntDef(
             prefix = {"OVERLAY_RESULT_"},
             value = {
@@ -803,6 +805,7 @@
     public @interface AttachOverlayResult {}
 
     /** Result code indicating the overlay was successfully attached. */
+    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
     public static final int OVERLAY_RESULT_SUCCESS = 0;
 
     /**
@@ -810,6 +813,7 @@
      * error and not
      * because of problems with the input.
      */
+    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
     public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;
 
     /**
@@ -817,6 +821,7 @@
      * specified display or
      * window id was invalid.
      */
+    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
     public static final int OVERLAY_RESULT_INVALID = 2;
 
     private int mConnectionId = AccessibilityInteractionClient.NO_ID;
@@ -3506,11 +3511,7 @@
      * @param displayId the display to which the SurfaceControl should be attached.
      * @param sc the SurfaceControl containing the overlay content
      *
-     * @deprecated Use
-     * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)}
-     * instead.
      */
-    @Deprecated
     public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
         Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
         AccessibilityInteractionClient.getInstance(this)
@@ -3547,6 +3548,7 @@
      * @see #OVERLAY_RESULT_INVALID
      * @see #OVERLAY_RESULT_INTERNAL_ERROR
      */
+    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
     public void attachAccessibilityOverlayToDisplay(
             int displayId,
             @NonNull SurfaceControl sc,
@@ -3581,11 +3583,7 @@
      * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
      * @param sc the SurfaceControl containing the overlay content
      *
-     * @deprecated Use
-     * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)}
-     * instead.
      */
-    @Deprecated
     public void attachAccessibilityOverlayToWindow(
             int accessibilityWindowId, @NonNull SurfaceControl sc) {
         Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
@@ -3623,6 +3621,7 @@
      * @see #OVERLAY_RESULT_INVALID
      * @see #OVERLAY_RESULT_INTERNAL_ERROR
      */
+    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
     public void attachAccessibilityOverlayToWindow(
             int accessibilityWindowId,
             @NonNull SurfaceControl sc,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 895dde7..f2c0051 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2073,9 +2073,7 @@
      * Allow a {@link PendingIntent} to use the privilege of its creator to start background
      * activities.
      *
-     * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
-     * @throws IllegalArgumentException is the value is not a valid
-     * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
+     * @param mode the mode being set
      */
     @NonNull
     public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
@@ -2088,7 +2086,7 @@
      * Returns the mode to start background activities granted by the creator of the
      * {@link PendingIntent}.
      *
-     * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+     * @return the mode currently set
      */
     public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
         return mPendingIntentCreatorBackgroundActivityStartMode;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index dd1e108..ecbc9b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1478,7 +1478,8 @@
             AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
 
     /**
-     * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered.
+     * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
+     * service.
      *
      * @hide
      */
@@ -1486,13 +1487,13 @@
             AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
-     * Allows the assistant app to get the training data from the trusted process to improve the
-     * hotword training model.
+     * Allows the privileged assistant app to receive the training data from the sandboxed hotword
+     * detection service.
      *
      * @hide
      */
-    public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
-            AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA;
+    public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+            AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1640,7 +1641,7 @@
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED,
             OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
-            OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA
+            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     })
     public @interface AppOpString {}
 
@@ -2257,18 +2258,17 @@
      *
      * @hide
      */
-    @SystemApi
     public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO =
             "android:receive_sandbox_trigger_audio";
 
     /**
-     * Allows the assistant app to get the training data from the trusted process to improve
-     * the hotword training model.
+     * Allows the privileged assistant app to receive training data from the sandboxed hotword
+     * detection service.
      *
      * @hide
      */
-    public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
-            "android:receive_trusted_process_training_data";
+    public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+            "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2381,7 +2381,8 @@
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
             OP_USE_FULL_SCREEN_INTENT,
-            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+            OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2814,9 +2815,11 @@
                 "RECEIVE_SANDBOX_TRIGGER_AUDIO")
                 .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
                 .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
-        new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
-                OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
-                "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build()
+        new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
+                .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
+                .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e5a73be..fd308ce 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -48,6 +48,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApkChecksum;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackage;
 import android.content.pm.ChangedPackages;
 import android.content.pm.Checksum;
 import android.content.pm.ComponentInfo;
@@ -2954,6 +2955,17 @@
         }
     }
 
+    @Override
+    public boolean isPackageStopped(@NonNull String packageName) throws NameNotFoundException {
+        try {
+            return mPM.isPackageStoppedForUser(packageName, getUserId());
+        } catch (IllegalArgumentException ie) {
+            throw new NameNotFoundException(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @Override
     public void setApplicationCategoryHint(String packageName, int categoryHint) {
@@ -3925,6 +3937,19 @@
     }
 
     @Override
+    public @Nullable ArchivedPackage getArchivedPackage(@NonNull String packageName) {
+        try {
+            var parcel = mPM.getArchivedPackage(packageName, mContext.getUserId());
+            if (parcel == null) {
+                return null;
+            }
+            return new ArchivedPackage(parcel);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
     public boolean canUserUninstall(String packageName, UserHandle user) {
         try {
             return mPM.getBlockUninstallForUser(packageName, user.getIdentifier());
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 37111e9..c8317c8 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -27,7 +27,15 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -608,6 +616,103 @@
                 }
             };
 
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition at {@link
+     * android.app.ApplicationStartInfoProto}
+     *
+     * @param proto Stream to write the ApplicationStartInfo object to.
+     * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
+        final long token = proto.start(fieldId);
+        proto.write(ApplicationStartInfoProto.PID, mPid);
+        proto.write(ApplicationStartInfoProto.REAL_UID, mRealUid);
+        proto.write(ApplicationStartInfoProto.PACKAGE_UID, mPackageUid);
+        proto.write(ApplicationStartInfoProto.DEFINING_UID, mDefiningUid);
+        proto.write(ApplicationStartInfoProto.PROCESS_NAME, mProcessName);
+        proto.write(ApplicationStartInfoProto.STARTUP_STATE, mStartupState);
+        proto.write(ApplicationStartInfoProto.REASON, mReason);
+        if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
+            ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
+            ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
+            timestampsOut.writeObject(mStartupTimestampsNs);
+            proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
+                    timestampsBytes.toByteArray());
+        }
+        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();
+        }
+        proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
+        proto.end(token);
+    }
+
+    /**
+     * Read from a protocol buffer input stream. Protocol buffer message definition at {@link
+     * android.app.ApplicationStartInfoProto}
+     *
+     * @param proto Stream to read the ApplicationStartInfo object from.
+     * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
+     * @hide
+     */
+    public void readFromProto(ProtoInputStream proto, long fieldId)
+            throws IOException, WireTypeMismatchException, ClassNotFoundException {
+        final long token = proto.start(fieldId);
+        while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (proto.getFieldNumber()) {
+                case (int) ApplicationStartInfoProto.PID:
+                    mPid = proto.readInt(ApplicationStartInfoProto.PID);
+                    break;
+                case (int) ApplicationStartInfoProto.REAL_UID:
+                    mRealUid = proto.readInt(ApplicationStartInfoProto.REAL_UID);
+                    break;
+                case (int) ApplicationStartInfoProto.PACKAGE_UID:
+                    mPackageUid = proto.readInt(ApplicationStartInfoProto.PACKAGE_UID);
+                    break;
+                case (int) ApplicationStartInfoProto.DEFINING_UID:
+                    mDefiningUid = proto.readInt(ApplicationStartInfoProto.DEFINING_UID);
+                    break;
+                case (int) ApplicationStartInfoProto.PROCESS_NAME:
+                    mProcessName = intern(proto.readString(ApplicationStartInfoProto.PROCESS_NAME));
+                    break;
+                case (int) ApplicationStartInfoProto.STARTUP_STATE:
+                    mStartupState = proto.readInt(ApplicationStartInfoProto.STARTUP_STATE);
+                    break;
+                case (int) ApplicationStartInfoProto.REASON:
+                    mReason = proto.readInt(ApplicationStartInfoProto.REASON);
+                    break;
+                case (int) ApplicationStartInfoProto.STARTUP_TIMESTAMPS:
+                    ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
+                            ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
+                    ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
+                    mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject();
+                    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();
+                    }
+                    break;
+                case (int) ApplicationStartInfoProto.LAUNCH_MODE:
+                    mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE);
+                    break;
+            }
+        }
+        proto.end(token);
+    }
+
     /** @hide */
     public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix,
             @NonNull SimpleDateFormat sdf) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9549ebf..41b4004 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -843,8 +843,7 @@
      * considered to be in the same delivery group as this iff it has the same {@code namespace}
      * and {@code key}.
      *
-     * <p> If neither matching key using this API nor matching filter using
-     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+     * <p> If not matching key using this API then by default
      * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
      */
     @NonNull
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a538247..08c18c8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@
 
         // only do this if the user already has more than one preferred locale
         if (r.getConfiguration().getLocales().size() > 1) {
-            LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
-            mResourcesManager.setLocaleList(lc != null
-                    && lc.getSupportedLocales() != null
-                    && !lc.getSupportedLocales().isEmpty()
-                    ? lc.getSupportedLocales()
-                    : null);
+            LocaleConfig lc = getUserId() < 0
+                    ? LocaleConfig.fromContextIgnoringOverride(this)
+                    : new LocaleConfig(this);
+            mResourcesManager.setLocaleConfig(lc);
         }
     }
 
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index e2082f7..180725e 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -706,8 +706,12 @@
     }
 
     private boolean allowOverlappingTransitions() {
-        return mIsReturning ? getWindow().getAllowReturnTransitionOverlap()
-                : getWindow().getAllowEnterTransitionOverlap();
+        final Window window = getWindow();
+        if (window == null) {
+            return false;
+        }
+        return mIsReturning ? window.getAllowReturnTransitionOverlap()
+                : window.getAllowEnterTransitionOverlap();
     }
 
     private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index a3b82e9..d7d6546 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -161,12 +161,6 @@
      */
     boolean isWallpaperBackupEligible(int which, int userId);
 
-    /*
-     * Keyguard: register a callback for being notified that lock-state relevant
-     * wallpaper content has changed.
-     */
-    boolean setLockWallpaperCallback(IWallpaperManagerCallback cb);
-
     /**
      * Returns the colors used by the lock screen or system wallpaper.
      *
@@ -253,13 +247,6 @@
     boolean isStaticWallpaper(int which);
 
     /**
-     * Temporary method for project b/197814683.
-     * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image.
-     * @hide
-     */
-     boolean isLockscreenLiveWallpaperEnabled();
-
-    /**
      * Temporary method for project b/270726737.
      * Return true if the wallpaper supports different crops for different display dimensions.
      * @hide
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8fea03b..ebf183f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -50,7 +50,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.security.net.config.NetworkSecurityConfigProvider;
-import android.sysprop.VndkProperties;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
@@ -901,14 +900,10 @@
         }
 
         // Similar to vendor apks, we should add /product/lib for apks from product partition
-        // when product apps are marked as unbundled. We cannot use the same way from vendor
-        // to check if lib path exists because there is possibility that /product/lib would not
-        // exist from legacy device while product apks are bundled. To make this clear, we use
-        // "ro.product.vndk.version" property. If the property is defined, we regard all product
-        // apks as unbundled.
+        // when product apps are marked as unbundled. Product is separated as long as the
+        // partition exists, so it can be handled with same approach from the vendor partition.
         if (mApplicationInfo.getCodePath() != null
-                && mApplicationInfo.isProduct()
-                && VndkProperties.product_vndk_version().isPresent()) {
+                && mApplicationInfo.isProduct()) {
             isBundledApp = false;
         }
 
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 0857c96..369a781 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -16,11 +16,12 @@
 
 package android.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -31,6 +32,7 @@
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -40,7 +42,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -67,6 +69,8 @@
     public static final String TAG_LOCALE_CONFIG = "locale-config";
     public static final String TAG_LOCALE = "locale";
     private LocaleList mLocales;
+
+    private Locale mDefaultLocale;
     private int mStatus = STATUS_NOT_SPECIFIED;
 
     /**
@@ -133,15 +137,14 @@
                 return;
             }
         }
-        int resId = 0;
         Resources res = context.getResources();
+        //Get the resource id
+        int resId = context.getApplicationInfo().getLocaleConfigRes();
+        if (resId == 0) {
+            mStatus = STATUS_NOT_SPECIFIED;
+            return;
+        }
         try {
-            //Get the resource id
-            resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
-            if (resId == 0) {
-                mStatus = STATUS_NOT_SPECIFIED;
-                return;
-            }
             //Get the parser to read XML data
             XmlResourceParser parser = res.getXml(resId);
             parseLocaleConfig(parser, res);
@@ -195,8 +198,17 @@
         XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
         int outerDepth = parser.getDepth();
         AttributeSet attrs = Xml.asAttributeSet(parser);
-        // LinkedHashSet to preserve insertion order
-        Set<String> localeNames = new LinkedHashSet<>();
+
+        String defaultLocale = null;
+        if (android.content.res.Flags.defaultLocale()) {
+            TypedArray att = res.obtainAttributes(
+                    attrs, com.android.internal.R.styleable.LocaleConfig);
+            defaultLocale = att.getString(
+                    R.styleable.LocaleConfig_defaultLocale);
+            att.recycle();
+        }
+
+        Set<String> localeNames = new HashSet<>();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (TAG_LOCALE.equals(parser.getName())) {
                 final TypedArray attributes = res.obtainAttributes(
@@ -211,6 +223,15 @@
         }
         mStatus = STATUS_SUCCESS;
         mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
+        if (defaultLocale != null) {
+            if (localeNames.contains(defaultLocale)) {
+                mDefaultLocale = Locale.forLanguageTag(defaultLocale);
+            } else {
+                Slog.w(TAG, "Default locale specified that is not contained in the list: "
+                        + defaultLocale);
+                mStatus = STATUS_PARSING_FAILED;
+            }
+        }
     }
 
     /**
@@ -226,6 +247,17 @@
     }
 
     /**
+     * Returns the default locale if specified, otherwise null
+     *
+     * @return The default Locale or null
+     */
+    @SuppressLint("UseIcu")
+    @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE)
+    public @Nullable Locale getDefaultLocale() {
+        return mDefaultLocale;
+    }
+
+    /**
      * Get the status of reading the resource file where the LocaleConfig was stored.
      *
      * <p>Distinguish "the application didn't provide the resource file" from "the application
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dd7db23..3bde39c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1514,13 +1514,13 @@
 
     /**
      * {@link #extras} key: the color used as a hint for the Answer action button of a
-     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+     * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
      */
     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
 
     /**
      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
-     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+     * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
      */
     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
 
@@ -1704,7 +1704,7 @@
         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
 
         /**
-         * {@link }: No semantic action defined.
+         * No semantic action defined.
          */
         public static final int SEMANTIC_ACTION_NONE = 0;
 
@@ -5607,7 +5607,8 @@
             // Use different highlighted colors for conversations' unread count
             if (p.mHighlightExpander) {
                 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
-                textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
+                textColor = Colors.flattenAlpha(
+                        getColors(p).getOnTertiaryAccentTextColor(), pillColor);
             }
             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -7923,7 +7924,7 @@
      * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
      * conversation section in the shade above non-conversation alerting and silence notifications.
      * To be a valid conversation shortcut, the shortcut must be a
-     * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut.
+     * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
      *
      * <p>
      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
@@ -12833,7 +12834,7 @@
         private int mPrimaryAccentColor = COLOR_INVALID;
         private int mSecondaryAccentColor = COLOR_INVALID;
         private int mTertiaryAccentColor = COLOR_INVALID;
-        private int mOnAccentTextColor = COLOR_INVALID;
+        private int mOnTertiaryAccentTextColor = COLOR_INVALID;
         private int mErrorColor = COLOR_INVALID;
         private int mContrastColor = COLOR_INVALID;
         private int mRippleAlpha = 0x33;
@@ -12908,7 +12909,7 @@
                 mPrimaryAccentColor = mPrimaryTextColor;
                 mSecondaryAccentColor = mSecondaryTextColor;
                 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
-                mOnAccentTextColor = mBackgroundColor;
+                mOnTertiaryAccentTextColor = mBackgroundColor;
                 mErrorColor = mPrimaryTextColor;
                 mRippleAlpha = 0x33;
             } else {
@@ -12930,7 +12931,7 @@
                     mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
                     mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
                     mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
-                    mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
+                    mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID);
                     mErrorColor = getColor(ta, 7, COLOR_INVALID);
                     mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
                 }
@@ -12955,8 +12956,8 @@
                 if (mTertiaryAccentColor == COLOR_INVALID) {
                     mTertiaryAccentColor = mContrastColor;
                 }
-                if (mOnAccentTextColor == COLOR_INVALID) {
-                    mOnAccentTextColor = ColorUtils.setAlphaComponent(
+                if (mOnTertiaryAccentTextColor == COLOR_INVALID) {
+                    mOnTertiaryAccentTextColor = ColorUtils.setAlphaComponent(
                             ContrastColorUtil.resolvePrimaryColor(
                                     ctx, mTertiaryAccentColor, nightMode), 0xFF);
                 }
@@ -13029,8 +13030,8 @@
         }
 
         /** @return the theme's text color to be used on the tertiary accent color. */
-        public @ColorInt int getOnAccentTextColor() {
-            return mOnAccentTextColor;
+        public @ColorInt int getOnTertiaryAccentTextColor() {
+            return mOnTertiaryAccentTextColor;
         }
 
         /**
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 7ee1332..15d692a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -272,6 +272,10 @@
     private boolean mDemoted = false;
     private boolean mImportantConvo = false;
     private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
+    /** Do not (de)serialize this value: it only affects logic in system_server and that logic
+     * is reset on each boot {@link NotificationAttentionHelper#buzzBeepBlinkLocked}.
+     */
+    private long mLastNotificationUpdateTimeMs = 0;
 
     /**
      * Creates a notification channel.
@@ -932,6 +936,23 @@
     }
 
     /**
+     * Returns the time of the notification post or last update for this channel.
+     * @return time of post / last update
+     * @hide
+     */
+    public long getLastNotificationUpdateTimeMs() {
+        return mLastNotificationUpdateTimeMs;
+    }
+
+    /**
+     * Sets the time of the notification post or last update for this channel.
+     * @hide
+     */
+    public void setLastNotificationUpdateTimeMs(long updateTimeMs) {
+        mLastNotificationUpdateTimeMs = updateTimeMs;
+    }
+
+    /**
      * @hide
      */
     public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled,
@@ -1408,7 +1429,8 @@
                 + ", mParent=" + mParentId
                 + ", mConversationId=" + mConversationId
                 + ", mDemoted=" + mDemoted
-                + ", mImportantConvo=" + mImportantConvo;
+                + ", mImportantConvo=" + mImportantConvo
+                + ", mLastNotificationUpdateTimeMs=" + mLastNotificationUpdateTimeMs;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 79b68c1..b8bea9d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -25,8 +25,12 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
 import android.annotation.WorkerThread;
 import android.app.Notification.Builder;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1659,23 +1663,42 @@
     }
 
     /**
+     * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the
+     * {@code setNotificationListenerAccessGranted} method will use the user contained within the
+     * context.
+     * For apps targeting an SDK version <em>below</em> this, the user of the calling process will
+     * be used (Process.myUserHandle()).
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE = 302563478L;
+
+    /**
      * Grants/revokes Notification Listener access to the given component for current user.
      * To grant access for a particular user, obtain this service by using the {@link Context}
      * provided by {@link Context#createPackageContextAsUser}
      *
      * @param listener Name of component to grant/revoke access
-     * @param granted Grant/revoke access
-     * @param userSet Whether the action was triggered explicitly by user
+     * @param granted  Grant/revoke access
+     * @param userSet  Whether the action was triggered explicitly by user
      * @hide
      */
     @SystemApi
     @TestApi
+    @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS)
     public void setNotificationListenerAccessGranted(
             @NonNull ComponentName listener, boolean granted, boolean userSet) {
         INotificationManager service = getService();
         try {
-            service.setNotificationListenerAccessGranted(listener, granted, userSet);
+            if (CompatChanges.isChangeEnabled(SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE)) {
+                service.setNotificationListenerAccessGrantedForUser(listener, mContext.getUserId(),
+                        granted, userSet);
+            } else {
+                service.setNotificationListenerAccessGranted(listener, granted, userSet);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d3..6009c29 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@
     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
 
     /**
-     * The list of locales the app declares it supports.
+     * The localeConfig of the app.
      */
-    private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+    private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
 
     private static class ApkKey {
         public final String path;
@@ -1612,18 +1612,19 @@
     }
 
     /**
-     * Returns the LocaleList current set
+     * Returns the LocaleConfig current set
      */
-    public LocaleList getLocaleList() {
-        return mLocaleList;
+    public LocaleConfig getLocaleConfig() {
+        return mLocaleConfig;
     }
 
     /**
-     * Sets the LocaleList of app's supported locales
+     * Sets the LocaleConfig of the app
      */
-    public void setLocaleList(LocaleList localeList) {
-        if ((localeList != null) && !localeList.isEmpty()) {
-            mLocaleList = localeList;
+    public void setLocaleConfig(LocaleConfig localeConfig) {
+        if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
+                && !localeConfig.getSupportedLocales().isEmpty()) {
+            mLocaleConfig = localeConfig;
         }
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e7a5b72..d660078 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -313,7 +313,6 @@
     private final Context mContext;
     private final boolean mWcgEnabled;
     private final ColorManagementProxy mCmProxy;
-    private static Boolean sIsLockscreenLiveWallpaperEnabled = null;
     private static Boolean sIsMultiCropEnabled = null;
 
     /**
@@ -841,29 +840,14 @@
     }
 
     /**
+     * TODO (b/305908217) remove
      * Temporary method for project b/197814683.
      * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
      * @hide
      */
     @TestApi
     public boolean isLockscreenLiveWallpaperEnabled() {
-        return isLockscreenLiveWallpaperEnabledHelper();
-    }
-
-    private static boolean isLockscreenLiveWallpaperEnabledHelper() {
-        if (sGlobals == null) {
-            sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
-                    "persist.wm.debug.lockscreen_live_wallpaper", true);
-        }
-        if (sIsLockscreenLiveWallpaperEnabled == null) {
-            try {
-                sIsLockscreenLiveWallpaperEnabled =
-                        sGlobals.mService.isLockscreenLiveWallpaperEnabled();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return sIsLockscreenLiveWallpaperEnabled;
+        return true;
     }
 
     /**
@@ -2446,12 +2430,7 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clearWallpaper() {
-        if (isLockscreenLiveWallpaperEnabled()) {
-            clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
-            return;
-        }
-        clearWallpaper(FLAG_LOCK, mContext.getUserId());
-        clearWallpaper(FLAG_SYSTEM, mContext.getUserId());
+        clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
     }
 
     /**
@@ -2787,11 +2766,7 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clear() throws IOException {
-        if (isLockscreenLiveWallpaperEnabled()) {
-            clear(FLAG_SYSTEM | FLAG_LOCK);
-            return;
-        }
-        setStream(openDefaultWallpaper(mContext, FLAG_SYSTEM), null, false);
+        clear(FLAG_SYSTEM | FLAG_LOCK);
     }
 
     /**
@@ -2816,16 +2791,7 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clear(@SetWallpaperFlags int which) throws IOException {
-        if (isLockscreenLiveWallpaperEnabled()) {
-            clearWallpaper(which, mContext.getUserId());
-            return;
-        }
-        if ((which & FLAG_SYSTEM) != 0) {
-            clear();
-        }
-        if ((which & FLAG_LOCK) != 0) {
-            clearWallpaper(FLAG_LOCK, mContext.getUserId());
-        }
+        clearWallpaper(which, mContext.getUserId());
     }
 
     /**
@@ -2840,16 +2806,12 @@
     public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
         final String whichProp;
         final int defaultResId;
-        if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) {
-            /* Factory-default lock wallpapers are not yet supported
-            whichProp = PROP_LOCK_WALLPAPER;
-            defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
-            */
-            return null;
-        } else {
-            whichProp = PROP_WALLPAPER;
-            defaultResId = com.android.internal.R.drawable.default_wallpaper;
-        }
+        /* Factory-default lock wallpapers are not yet supported.
+        whichProp = which == FLAG_LOCK ? PROP_LOCK_WALLPAPER : PROP_WALLPAPER;
+        defaultResId = which == FLAG_LOCK ? R.drawable.default_lock_wallpaper :  ....
+        */
+        whichProp = PROP_WALLPAPER;
+        defaultResId = R.drawable.default_wallpaper;
         final String path = SystemProperties.get(whichProp);
         final InputStream wallpaperInputStream = getWallpaperInputStream(path);
         if (wallpaperInputStream != null) {
@@ -2988,25 +2950,6 @@
     }
 
     /**
-     * Register a callback for lock wallpaper observation. Only the OS may use this.
-     *
-     * @return true on success; false on error.
-     * @hide
-     */
-    public boolean setLockWallpaperCallback(IWallpaperManagerCallback callback) {
-        if (sGlobals.mService == null) {
-            Log.w(TAG, "WallpaperService not running");
-            throw new RuntimeException(new DeadSystemException());
-        }
-
-        try {
-            return sGlobals.mService.setLockWallpaperCallback(callback);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Is the current system wallpaper eligible for backup?
      *
      * Only the OS itself may use this method.
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index ad0af72..84b1ca5 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,10 +16,13 @@
 
 package android.app.admin;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.UserManager;
 
+
 import java.util.Objects;
 
 /**
@@ -164,6 +167,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
     @TestApi
     public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
 
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 456c6af..7c69d37 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1172,6 +1172,12 @@
             public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER";
 
             /**
+             * Header for items under the private user
+             */
+            public static final String PRIVATE_CATEGORY_HEADER =
+                    PREFIX + "PRIVATE_CATEGORY_HEADER";
+
+            /**
              * Header for items under the personal user
              */
             public static final String PERSONAL_CATEGORY_HEADER =
@@ -1208,6 +1214,12 @@
             public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA";
 
             /**
+             * Text for toggle to enable auto-sycing private data
+             */
+            public static final String AUTO_SYNC_PRIVATE_DATA = PREFIX
+                    + "AUTO_SYNC_PRIVATE_DATA";
+
+            /**
              * Summary for "More security settings" section when a work profile is on the device.
              */
             public static final String MORE_SECURITY_SETTINGS_WORK_PROFILE_SUMMARY = PREFIX
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index c760298..7320cea 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -51,7 +51,7 @@
 /**
  * Determines when over-the-air system updates are installed on a device. Only a device policy
  * controller (DPC) running in device owner mode or in profile owner mode for an organization-owned
- * device can set an update policy for the device—by calling the {@code DevicePolicyManager} method
+ * device can set an update policy for the device by calling the {@code DevicePolicyManager} method
  * {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update
  * policy affects the pending system update (if there is one) and any future updates for the device.
  *
@@ -125,7 +125,7 @@
      *
      * <p>The system limits each update to one 30-day postponement. The period begins when the
      * system first postpones the update and setting new {@code TYPE_POSTPONE} policies won’t extend
-     * the period. If, after 30 days the update isn’t installed (through policy changes), the system
+     * the period. If, after 30 days the update isn't installed (through policy changes), the system
      * prompts the user to install the update.
      *
      * <p><strong>Note</strong>: Device manufacturers or carriers might choose to exempt important
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
new file mode 100644
index 0000000..5a41c65
--- /dev/null
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -0,0 +1,43 @@
+package: "android.app.admin.flags"
+
+flag {
+  name: "policy_engine_migration_v2_enabled"
+  namespace: "enterprise"
+  description: "V2 of the policy engine migrations for Android V"
+  bug: "289520697"
+}
+
+flag {
+  name: "device_policy_size_tracking_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track the total policy size and have a max threshold."
+  bug: "281543351"
+}
+
+flag {
+  name: "onboarding_bugreport_v2_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
+  bug: "302517677"
+}
+
+flag {
+  name: "cross_user_suspension_enabled"
+  namespace: "enterprise"
+  description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
+  bug: "263464464"
+}
+
+flag {
+  name: "dedicated_device_control_enabled"
+  namespace: "enterprise"
+  description: "Allow the device management role holder to control which platform features are available on dedicated devices."
+  bug: "281964214"
+}
+
+flag {
+  name: "security_log_v2_enabled"
+  namespace: "enterprise"
+  description: "Improve access to security logging in the context of Zero Trust."
+  bug: "295324350"
+}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 15bd1dc..e268968 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -651,6 +651,7 @@
         // POJO used to override some autofill-related values when the node is parcelized.
         // Not written to parcel.
         AutofillOverlay mAutofillOverlay;
+        boolean mIsCredential;
 
         int mX;
         int mY;
@@ -799,6 +800,7 @@
 
             if (autofillFlags != 0) {
                 mSanitized = in.readInt() == 1;
+                mIsCredential = in.readInt() == 1;
                 mImportantForAutofill = in.readInt();
 
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1033,6 +1035,7 @@
 
             if (autofillFlags != 0) {
                 out.writeInt(mSanitized ? 1 : 0);
+                out.writeInt(mIsCredential ? 1 : 0);
                 out.writeInt(mImportantForAutofill);
                 writeSensitive = mSanitized || !sanitizeOnWrite;
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1246,6 +1249,19 @@
         }
 
         /**
+         * @return whether the node is a credential.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         * TODO(b/303677885): add TestApi
+         *
+         * @hide
+         */
+        public boolean isCredential() {
+            return mIsCredential;
+        }
+
+        /**
          * Gets the {@link android.text.InputType} bits of this structure.
          *
          * @return bits as defined by {@link android.text.InputType}.
@@ -2183,6 +2199,11 @@
         }
 
         @Override
+        public void setIsCredential(boolean isCredential) {
+            mNode.mIsCredential = isCredential;
+        }
+
+        @Override
         public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) {
             mNode.mReceiveContentMimeTypes = mimeTypes;
         }
@@ -2498,7 +2519,9 @@
                     + ", value=" + node.getAutofillValue()
                     + ", sanitized=" + node.isSanitized()
                     + ", important=" + node.getImportantForAutofill()
-                    + ", visibility=" + node.getVisibility());
+                    + ", visibility=" + node.getVisibility()
+                    + ", isCredential=" + node.isCredential()
+            );
         }
 
         final int NCHILDREN = node.getChildCount();
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index cf54c2a..5f8de77 100644
--- a/core/java/android/app/contentsuggestions/OWNERS
+++ b/core/java/android/app/contentsuggestions/OWNERS
@@ -1,7 +1,4 @@
 # Bug component: 643919
 
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
+hackz@google.com
+volnov@google.com
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index b34f678..06bff5d 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -58,6 +58,11 @@
         super(in);
     }
 
+    @Override
+    boolean isActivityLifecycleItem() {
+        return true;
+    }
+
     /** A final lifecycle state that an activity should reach. */
     @LifecycleState
     public abstract int getTargetState();
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 8617386..9c0cd39 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -45,6 +45,13 @@
  */
 public class ClientTransaction implements Parcelable, ObjectPoolItem {
 
+    /**
+     * List of transaction items that should be executed in order. Including both
+     * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
+     */
+    @Nullable
+    private List<ClientTransactionItem> mTransactionItems;
+
     /** A list of individual callbacks to a client. */
     @UnsupportedAppUsage
     private List<ClientTransactionItem> mActivityCallbacks;
@@ -64,9 +71,32 @@
     }
 
     /**
-     * Add a message to the end of the sequence of callbacks.
-     * @param activityCallback A single message that can contain a lifecycle request/callback.
+     * Adds a message to the end of the sequence of transaction items.
+     * @param item A single message that can contain a client activity/window request/callback.
+     * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
      */
+    public void addTransactionItem(@NonNull ClientTransactionItem item) {
+        if (mTransactionItems == null) {
+            mTransactionItems = new ArrayList<>();
+        }
+        mTransactionItems.add(item);
+    }
+
+    /**
+     * Gets the list of client window requests/callbacks.
+     * TODO(b/260873529): must be non null after remove the deprecated methods.
+     */
+    @Nullable
+    public List<ClientTransactionItem> getTransactionItems() {
+        return mTransactionItems;
+    }
+
+    /**
+     * Adds a message to the end of the sequence of callbacks.
+     * @param activityCallback A single message that can contain a lifecycle request/callback.
+     * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
+     */
+    @Deprecated
     public void addCallback(@NonNull ClientTransactionItem activityCallback) {
         if (mActivityCallbacks == null) {
             mActivityCallbacks = new ArrayList<>();
@@ -74,25 +104,35 @@
         mActivityCallbacks.add(activityCallback);
     }
 
-    /** Get the list of callbacks. */
+    /**
+     * Gets the list of callbacks.
+     * @deprecated use {@link #getTransactionItems()} instead.
+     */
     @Nullable
     @VisibleForTesting
     @UnsupportedAppUsage
+    @Deprecated
     public List<ClientTransactionItem> getCallbacks() {
         return mActivityCallbacks;
     }
 
-    /** Get the target state lifecycle request. */
+    /**
+     * Gets the target state lifecycle request.
+     * @deprecated use {@link #getTransactionItems()} instead.
+     */
     @VisibleForTesting(visibility = PACKAGE)
     @UnsupportedAppUsage
+    @Deprecated
     public ActivityLifecycleItem getLifecycleStateRequest() {
         return mLifecycleStateRequest;
     }
 
     /**
-     * Set the lifecycle state in which the client should be after executing the transaction.
+     * Sets the lifecycle state in which the client should be after executing the transaction.
      * @param stateRequest A lifecycle request initialized with right parameters.
+     * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
      */
+    @Deprecated
     public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
         mLifecycleStateRequest = stateRequest;
     }
@@ -103,6 +143,14 @@
      *                                 requested by transaction items.
      */
     public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
+        if (mTransactionItems != null) {
+            final int size = mTransactionItems.size();
+            for (int i = 0; i < size; ++i) {
+                mTransactionItems.get(i).preExecute(clientTransactionHandler);
+            }
+            return;
+        }
+
         if (mActivityCallbacks != null) {
             final int size = mActivityCallbacks.size();
             for (int i = 0; i < size; ++i) {
@@ -147,6 +195,13 @@
 
     @Override
     public void recycle() {
+        if (mTransactionItems != null) {
+            int size = mTransactionItems.size();
+            for (int i = 0; i < size; i++) {
+                mTransactionItems.get(i).recycle();
+            }
+            mTransactionItems = null;
+        }
         if (mActivityCallbacks != null) {
             int size = mActivityCallbacks.size();
             for (int i = 0; i < size; i++) {
@@ -165,8 +220,15 @@
     // Parcelable implementation
 
     /** Write to Parcel. */
+    @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        final boolean writeTransactionItems = mTransactionItems != null;
+        dest.writeBoolean(writeTransactionItems);
+        if (writeTransactionItems) {
+            dest.writeParcelableList(mTransactionItems, flags);
+        }
+
         dest.writeParcelable(mLifecycleStateRequest, flags);
         final boolean writeActivityCallbacks = mActivityCallbacks != null;
         dest.writeBoolean(writeActivityCallbacks);
@@ -177,11 +239,20 @@
 
     /** Read from Parcel. */
     private ClientTransaction(@NonNull Parcel in) {
-        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
+        final boolean readTransactionItems = in.readBoolean();
+        if (readTransactionItems) {
+            mTransactionItems = new ArrayList<>();
+            in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
+                    ClientTransactionItem.class);
+        }
+
+        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
+                ActivityLifecycleItem.class);
         final boolean readActivityCallbacks = in.readBoolean();
         if (readActivityCallbacks) {
             mActivityCallbacks = new ArrayList<>();
-            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
+            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
+                    ClientTransactionItem.class);
         }
     }
 
@@ -209,7 +280,8 @@
             return false;
         }
         final ClientTransaction other = (ClientTransaction) o;
-        return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+        return Objects.equals(mTransactionItems, other.mTransactionItems)
+                && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
                 && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
                 && mClient == other.mClient;
     }
@@ -217,6 +289,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + Objects.hashCode(mTransactionItems);
         result = 31 * result + Objects.hashCode(mActivityCallbacks);
         result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
         result = 31 * result + Objects.hashCode(mClient);
@@ -227,6 +300,22 @@
     void dump(@NonNull String prefix, @NonNull PrintWriter pw,
             @NonNull ClientTransactionHandler transactionHandler) {
         pw.append(prefix).println("ClientTransaction{");
+        if (mTransactionItems != null) {
+            pw.append(prefix).print("  transactionItems=[");
+            final String itemPrefix = prefix + "    ";
+            final int size = mTransactionItems.size();
+            if (size > 0) {
+                pw.println();
+                for (int i = 0; i < size; i++) {
+                    mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
+                }
+                pw.append(prefix).println("  ]");
+            } else {
+                pw.println("]");
+            }
+            pw.append(prefix).println("}");
+            return;
+        }
         pw.append(prefix).print("  callbacks=[");
         final String itemPrefix = prefix + "    ";
         final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 07e5a7d..f94e22d 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -72,6 +72,13 @@
         return null;
     }
 
+    /**
+     * Whether this is a {@link ActivityLifecycleItem}.
+     */
+    boolean isActivityLifecycleItem() {
+        return false;
+    }
+
     /** Dumps this transaction item. */
     void dump(@NonNull String prefix, @NonNull PrintWriter pw,
             @NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index e2aee83..7418c06 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -20,46 +20,31 @@
 
 import static java.util.Objects.requireNonNull;
 
-import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.app.ActivityThread;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Process;
-import android.util.ArrayMap;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
-import java.util.function.IntConsumer;
-
 /**
  * Singleton controller to manage listeners to individual {@link ClientTransaction}.
  *
- * TODO(b/260873529) make as TestApi to allow CTS.
  * @hide
  */
 public class ClientTransactionListenerController {
 
     private static ClientTransactionListenerController sController;
 
-    private final Object mLock = new Object();
-
-    /**
-     * Mapping from client registered listener for display change to the corresponding
-     * {@link Executor} to invoke the listener on.
-     * @see #registerDisplayChangeListener(IntConsumer, Executor)
-     */
-    @GuardedBy("mLock")
-    private final ArrayMap<IntConsumer, Executor> mDisplayChangeListeners = new ArrayMap<>();
-
-    private final ArrayList<IntConsumer> mTmpDisplayChangeListeners = new ArrayList<>();
+    private final DisplayManagerGlobal mDisplayManager;
 
     /** Gets the singleton controller. */
     @NonNull
     public static ClientTransactionListenerController getInstance() {
         synchronized (ClientTransactionListenerController.class) {
             if (sController == null) {
-                sController = new ClientTransactionListenerController();
+                sController = new ClientTransactionListenerController(
+                        DisplayManagerGlobal.getInstance());
             }
             return sController;
         }
@@ -68,45 +53,13 @@
     /** Creates a new instance for test only. */
     @VisibleForTesting
     @NonNull
-    public static ClientTransactionListenerController createInstanceForTesting() {
-        return new ClientTransactionListenerController();
+    public static ClientTransactionListenerController createInstanceForTesting(
+            @NonNull DisplayManagerGlobal displayManager) {
+        return new ClientTransactionListenerController(displayManager);
     }
 
-    private ClientTransactionListenerController() {}
-
-    /**
-     * Registers a new listener for display change. It will be invoked when receives a
-     * {@link ClientTransaction} that is updating display-related window configuration, such as
-     * bounds and rotation.
-     *
-     * WHen triggered, the listener will be invoked with the logical display id that was changed.
-     *
-     * @param listener the listener to invoke when receives a transaction with Display change.
-     * @param executor the executor on which callback method will be invoked.
-     */
-    public void registerDisplayChangeListener(@NonNull IntConsumer listener,
-            @NonNull @CallbackExecutor Executor executor) {
-        if (!isSyncWindowConfigUpdateFlagEnabled()) {
-            return;
-        }
-        requireNonNull(listener);
-        requireNonNull(executor);
-        synchronized (mLock) {
-            mDisplayChangeListeners.put(listener, executor);
-        }
-    }
-
-    /**
-     * Unregisters the listener for display change that was previously registered through
-     * {@link #registerDisplayChangeListener}.
-     */
-    public void unregisterDisplayChangeListener(@NonNull IntConsumer listener) {
-        if (!isSyncWindowConfigUpdateFlagEnabled()) {
-            return;
-        }
-        synchronized (mLock) {
-            mDisplayChangeListeners.remove(listener);
-        }
+    private ClientTransactionListenerController(@NonNull DisplayManagerGlobal displayManager) {
+        mDisplayManager = requireNonNull(displayManager);
     }
 
     /**
@@ -117,24 +70,14 @@
         if (!isSyncWindowConfigUpdateFlagEnabled()) {
             return;
         }
-        synchronized (mLock) {
-            // Make a copy of the list to avoid listener removal during callback.
-            mTmpDisplayChangeListeners.addAll(mDisplayChangeListeners.keySet());
-            final int num = mTmpDisplayChangeListeners.size();
-            try {
-                for (int i = 0; i < num; i++) {
-                    final IntConsumer listener = mTmpDisplayChangeListeners.get(i);
-                    final Executor executor = mDisplayChangeListeners.get(listener);
-                    executor.execute(() -> listener.accept(displayId));
-                }
-            } finally {
-                mTmpDisplayChangeListeners.clear();
-            }
+        if (ActivityThread.isSystem()) {
+            // Not enable for system server.
+            return;
         }
+        mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
     }
 
     /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */
-    @VisibleForTesting
     public boolean isSyncWindowConfigUpdateFlagEnabled() {
         // Can't read flag from isolated process.
         return !Process.isIsolated() && syncWindowConfigUpdateFlag();
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 066f9fe..9f5e0dc 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -28,6 +28,7 @@
 import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
 import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
 import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
+import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState;
 import static android.app.servertransaction.TransactionExecutorHelper.tId;
 import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
 
@@ -61,6 +62,9 @@
     private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
     private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
 
+    /** Keeps track of display ids whose Configuration got updated within a transaction. */
+    private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+
     /** Initialize an instance with transaction handler, that will execute all requested actions. */
     public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
         mTransactionHandler = clientTransactionHandler;
@@ -79,15 +83,52 @@
             Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
         }
 
-        executeCallbacks(transaction);
-        executeLifecycleState(transaction);
+        if (transaction.getTransactionItems() != null) {
+            executeTransactionItems(transaction);
+        } else {
+            // TODO(b/260873529): cleanup after launch.
+            executeCallbacks(transaction);
+            executeLifecycleState(transaction);
+        }
+
+        if (!mConfigUpdatedDisplayIds.isEmpty()) {
+            // Whether this transaction should trigger DisplayListener#onDisplayChanged.
+            final ClientTransactionListenerController controller =
+                    ClientTransactionListenerController.getInstance();
+            final int displayCount = mConfigUpdatedDisplayIds.size();
+            for (int i = 0; i < displayCount; i++) {
+                final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
+                controller.onDisplayChanged(displayId);
+            }
+            mConfigUpdatedDisplayIds.clear();
+        }
 
         mPendingActions.clear();
         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
     }
 
-    /** Cycle through all states requested by callbacks and execute them at proper times. */
+    /** Cycles through all transaction items and execute them at proper times. */
     @VisibleForTesting
+    public void executeTransactionItems(@NonNull ClientTransaction transaction) {
+        final List<ClientTransactionItem> items = transaction.getTransactionItems();
+        final int size = items.size();
+        for (int i = 0; i < size; i++) {
+            final ClientTransactionItem item = items.get(i);
+            if (item.isActivityLifecycleItem()) {
+                executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
+            } else {
+                executeNonLifecycleItem(transaction, item,
+                        shouldExcludeLastLifecycleState(items, i));
+            }
+        }
+    }
+
+    /**
+     * Cycle through all states requested by callbacks and execute them at proper times.
+     * @deprecated use {@link #executeTransactionItems} instead.
+     */
+    @VisibleForTesting
+    @Deprecated
     public void executeCallbacks(@NonNull ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
         if (callbacks == null || callbacks.isEmpty()) {
@@ -105,83 +146,78 @@
         // Index of the last callback that requests some post-execution state.
         final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
 
-        // Keep track of display ids whose Configuration got updated with this transaction.
-        ArraySet<Integer> configUpdatedDisplays = null;
-
         final int size = callbacks.size();
         for (int i = 0; i < size; ++i) {
             final ClientTransactionItem item = callbacks.get(i);
-            final IBinder token = item.getActivityToken();
-            ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
 
-            if (token != null && r == null
-                    && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
-                // The activity has not been created but has been requested to destroy, so all
-                // transactions for the token are just like being cancelled.
-                Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
-                continue;
-            }
-
-            if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+            // Skip the very last transition and perform it by explicit state request instead.
             final int postExecutionState = item.getPostExecutionState();
-
-            if (item.shouldHaveDefinedPreExecutionState()) {
-                final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
-                        item.getPostExecutionState());
-                if (closestPreExecutionState != UNDEFINED) {
-                    cycleToPath(r, closestPreExecutionState, transaction);
-                }
-            }
-
-            // Can't read flag from isolated process.
-            final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
-                    && syncWindowConfigUpdateFlag();
-            final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
-                    ? item.getContextToUpdate(mTransactionHandler)
-                    : null;
-            final Configuration preExecutedConfig = configUpdatedContext != null
-                    ? new Configuration(configUpdatedContext.getResources().getConfiguration())
-                    : null;
-
-            item.execute(mTransactionHandler, mPendingActions);
-
-            if (configUpdatedContext != null) {
-                final Configuration postExecutedConfig = configUpdatedContext.getResources()
-                        .getConfiguration();
-                if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
-                    if (configUpdatedDisplays == null) {
-                        configUpdatedDisplays = new ArraySet<>();
-                    }
-                    configUpdatedDisplays.add(configUpdatedContext.getDisplayId());
-                }
-            }
-
-            item.postExecute(mTransactionHandler, mPendingActions);
-            if (r == null) {
-                // Launch activity request will create an activity record.
-                r = mTransactionHandler.getActivityClient(token);
-            }
-
-            if (postExecutionState != UNDEFINED && r != null) {
-                // Skip the very last transition and perform it by explicit state request instead.
-                final boolean shouldExcludeLastTransition =
-                        i == lastCallbackRequestingState && finalState == postExecutionState;
-                cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
-            }
-        }
-
-        if (configUpdatedDisplays != null) {
-            final ClientTransactionListenerController controller =
-                    ClientTransactionListenerController.getInstance();
-            final int displayCount = configUpdatedDisplays.size();
-            for (int i = 0; i < displayCount; i++) {
-                final int displayId = configUpdatedDisplays.valueAt(i);
-                controller.onDisplayChanged(displayId);
-            }
+            final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED
+                    && i == lastCallbackRequestingState && finalState == postExecutionState;
+            executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState);
         }
     }
 
-    /** Transition to the final state if requested by the transaction. */
+    private void executeNonLifecycleItem(@NonNull ClientTransaction transaction,
+            @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) {
+        final IBinder token = item.getActivityToken();
+        ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+
+        if (token != null && r == null
+                && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+            // The activity has not been created but has been requested to destroy, so all
+            // transactions for the token are just like being cancelled.
+            Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+            return;
+        }
+
+        if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+        final int postExecutionState = item.getPostExecutionState();
+
+        if (item.shouldHaveDefinedPreExecutionState()) {
+            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+                    postExecutionState);
+            if (closestPreExecutionState != UNDEFINED) {
+                cycleToPath(r, closestPreExecutionState, transaction);
+            }
+        }
+
+        // Can't read flag from isolated process.
+        final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
+                && syncWindowConfigUpdateFlag();
+        final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+                ? item.getContextToUpdate(mTransactionHandler)
+                : null;
+        final Configuration preExecutedConfig = configUpdatedContext != null
+                ? new Configuration(configUpdatedContext.getResources().getConfiguration())
+                : null;
+
+        item.execute(mTransactionHandler, mPendingActions);
+
+        if (configUpdatedContext != null) {
+            final Configuration postExecutedConfig = configUpdatedContext.getResources()
+                    .getConfiguration();
+            if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
+                mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
+            }
+        }
+
+        item.postExecute(mTransactionHandler, mPendingActions);
+        if (r == null) {
+            // Launch activity request will create an activity record.
+            r = mTransactionHandler.getActivityClient(token);
+        }
+
+        if (postExecutionState != UNDEFINED && r != null) {
+            cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction);
+        }
+    }
+
+    /**
+     * Transition to the final state if requested by the transaction.
+     * @deprecated use {@link #executeTransactionItems} instead
+     */
+    @Deprecated
     private void executeLifecycleState(@NonNull ClientTransaction transaction) {
         final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
         if (lifecycleItem == null) {
@@ -189,6 +225,11 @@
             return;
         }
 
+        executeLifecycleItem(transaction, lifecycleItem);
+    }
+
+    private void executeLifecycleItem(@NonNull ClientTransaction transaction,
+            @NonNull ActivityLifecycleItem lifecycleItem) {
         final IBinder token = lifecycleItem.getActivityToken();
         final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
         if (DEBUG_RESOLVER) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 7e89a5b..dfbccb4 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -236,21 +236,39 @@
      * index 1 will be returned, because ActivityResult request on position 1 will be the last
      * request that moves activity to the RESUMED state where it will eventually end.
      */
-    static int lastCallbackRequestingState(ClientTransaction transaction) {
+    static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
-        if (callbacks == null || callbacks.size() == 0) {
+        if (callbacks == null || callbacks.isEmpty()
+                || transaction.getLifecycleStateRequest() == null) {
             return -1;
         }
+        return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1,
+                transaction.getLifecycleStateRequest().getActivityToken());
+    }
 
+    /**
+     * Returns the index of the last callback between the start index and last index that requests
+     * the state for the given activity token in which that activity will be after execution.
+     * If there is a group of callbacks in the end that requests the same specific state or doesn't
+     * request any - we will find the first one from such group.
+     *
+     * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
+     * specific state. If there is a sequence
+     *   Configuration - ActivityResult - Configuration - ActivityResult
+     * index 1 will be returned, because ActivityResult request on position 1 will be the last
+     * request that moves activity to the RESUMED state where it will eventually end.
+     */
+    private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
+            int startIndex, int lastIndex, @NonNull IBinder activityToken) {
         // Go from the back of the list to front, look for the request closes to the beginning that
         // requests the state in which activity will end after all callbacks are executed.
         int lastRequestedState = UNDEFINED;
         int lastRequestingCallback = -1;
-        for (int i = callbacks.size() - 1; i >= 0; i--) {
-            final ClientTransactionItem callback = callbacks.get(i);
-            final int postExecutionState = callback.getPostExecutionState();
-            if (postExecutionState != UNDEFINED) {
-                // Found a callback that requests some post-execution state.
+        for (int i = lastIndex; i >= startIndex; i--) {
+            final ClientTransactionItem item = items.get(i);
+            final int postExecutionState = item.getPostExecutionState();
+            if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
+                // Found a callback that requests some post-execution state for the given activity.
                 if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
                     // It's either a first-from-end callback that requests state or it requests
                     // the same state as the last one. In both cases, we will use it as the new
@@ -266,6 +284,53 @@
         return lastRequestingCallback;
     }
 
+    /**
+     * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
+     * whether or not to exclude the last state. This only returns {@code true} when there is a
+     * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
+     * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
+     */
+    static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
+            int currentIndex) {
+        final ClientTransactionItem item = items.get(currentIndex);
+        final IBinder activityToken = item.getActivityToken();
+        final int postExecutionState = item.getPostExecutionState();
+        if (activityToken == null || postExecutionState == UNDEFINED) {
+            // Not a transaction item requesting post execution state.
+            return false;
+        }
+        final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
+                activityToken);
+        if (nextLifecycleItemIndex == -1) {
+            // No following ActivityLifecycleItem for this activity token.
+            return false;
+        }
+        final ActivityLifecycleItem lifecycleItem =
+                (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
+        if (postExecutionState != lifecycleItem.getTargetState()) {
+            // The explicit ActivityLifecycleItem is not requesting the same state.
+            return false;
+        }
+        // Only exclude for the first non-lifecycle item that requests the same specific state.
+        return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
+                nextLifecycleItemIndex - 1, activityToken);
+    }
+
+    /**
+     * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
+     */
+    private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
+            int startIndex, @NonNull IBinder activityToken) {
+        final int size = items.size();
+        for (int i = startIndex; i < size; i++) {
+            final ClientTransactionItem item = items.get(i);
+            if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     /** Dump transaction to string. */
     static String transactionToString(@NonNull ClientTransaction transaction,
             @NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
new file mode 100644
index 0000000..9828133
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import java.util.Objects;
+
+/**
+ * Message to deliver window resize info.
+ * @hide
+ */
+public class WindowStateResizeItem extends ClientTransactionItem {
+
+    private IWindow mWindow;
+    private ClientWindowFrames mFrames;
+    private boolean mReportDraw;
+    private MergedConfiguration mConfiguration;
+    private InsetsState mInsetsState;
+    private boolean mForceLayout;
+    private boolean mAlwaysConsumeSystemBars;
+    private int mDisplayId;
+    private int mSyncSeqId;
+    private boolean mDragResizing;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client,
+            @NonNull PendingTransactionActions pendingActions) {
+        try {
+            mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout,
+                    mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
+        } catch (RemoteException e) {
+            // Should be a local call.
+            throw new RuntimeException(e);
+        }
+    }
+
+    // ObjectPoolItem implementation
+
+    private WindowStateResizeItem() {}
+
+    /** Obtains an instance initialized with provided params. */
+    public static WindowStateResizeItem obtain(@NonNull IWindow window,
+            @NonNull ClientWindowFrames frames, boolean reportDraw,
+            @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState,
+            boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+            boolean dragResizing) {
+        WindowStateResizeItem instance =
+                ObjectPool.obtain(WindowStateResizeItem.class);
+        if (instance == null) {
+            instance = new WindowStateResizeItem();
+        }
+        instance.mWindow = requireNonNull(window);
+        instance.mFrames = requireNonNull(frames);
+        instance.mReportDraw = reportDraw;
+        instance.mConfiguration = requireNonNull(configuration);
+        instance.mInsetsState = requireNonNull(insetsState);
+        instance.mForceLayout = forceLayout;
+        instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        instance.mDisplayId = displayId;
+        instance.mSyncSeqId = syncSeqId;
+        instance.mDragResizing = dragResizing;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mWindow = null;
+        mFrames = null;
+        mReportDraw = false;
+        mConfiguration = null;
+        mInsetsState = null;
+        mForceLayout = false;
+        mAlwaysConsumeSystemBars = false;
+        mDisplayId = INVALID_DISPLAY;
+        mSyncSeqId = -1;
+        mDragResizing = false;
+        ObjectPool.recycle(this);
+    }
+
+    // Parcelable implementation
+
+    /** Writes to Parcel. */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mWindow.asBinder());
+        dest.writeTypedObject(mFrames, flags);
+        dest.writeBoolean(mReportDraw);
+        dest.writeTypedObject(mConfiguration, flags);
+        dest.writeTypedObject(mInsetsState, flags);
+        dest.writeBoolean(mForceLayout);
+        dest.writeBoolean(mAlwaysConsumeSystemBars);
+        dest.writeInt(mDisplayId);
+        dest.writeInt(mSyncSeqId);
+        dest.writeBoolean(mDragResizing);
+    }
+
+    /** Reads from Parcel. */
+    private WindowStateResizeItem(@NonNull Parcel in) {
+        mWindow = IWindow.Stub.asInterface(in.readStrongBinder());
+        mFrames = in.readTypedObject(ClientWindowFrames.CREATOR);
+        mReportDraw = in.readBoolean();
+        mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR);
+        mInsetsState = in.readTypedObject(InsetsState.CREATOR);
+        mForceLayout = in.readBoolean();
+        mAlwaysConsumeSystemBars = in.readBoolean();
+        mDisplayId = in.readInt();
+        mSyncSeqId = in.readInt();
+        mDragResizing = in.readBoolean();
+    }
+
+    public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() {
+        public WindowStateResizeItem createFromParcel(@NonNull Parcel in) {
+            return new WindowStateResizeItem(in);
+        }
+
+        public WindowStateResizeItem[] newArray(int size) {
+            return new WindowStateResizeItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowStateResizeItem other = (WindowStateResizeItem) o;
+        return Objects.equals(mWindow, other.mWindow)
+                && Objects.equals(mFrames, other.mFrames)
+                && mReportDraw == other.mReportDraw
+                && Objects.equals(mConfiguration, other.mConfiguration)
+                && Objects.equals(mInsetsState, other.mInsetsState)
+                && mForceLayout == other.mForceLayout
+                && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars
+                && mDisplayId == other.mDisplayId
+                && mSyncSeqId == other.mSyncSeqId
+                && mDragResizing == other.mDragResizing;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mWindow);
+        result = 31 * result + Objects.hashCode(mFrames);
+        result = 31 * result + (mReportDraw ? 1 : 0);
+        result = 31 * result + Objects.hashCode(mConfiguration);
+        result = 31 * result + Objects.hashCode(mInsetsState);
+        result = 31 * result + (mForceLayout ? 1 : 0);
+        result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0);
+        result = 31 * result + mDisplayId;
+        result = 31 * result + mSyncSeqId;
+        result = 31 * result + (mDragResizing ? 1 : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "WindowStateResizeItem{window=" + mWindow
+                + ", reportDrawn=" + mReportDraw
+                + ", configuration=" + mConfiguration
+                + "}";
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 3c66a15..f6f65c7 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -16,10 +16,12 @@
 package android.app.smartspace;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.smartspace.flags.Flags;
 import android.app.smartspace.uitemplatedata.BaseTemplateData;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -27,6 +29,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.widget.RemoteViews;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -41,7 +44,8 @@
  * {@link SmartspaceAction} as their type because they can have associated actions.
  *
  * <p><b>NOTE: </b>
- * If {@link mWidget} is set, it should be preferred over all other properties.
+ * If either {@link mRemoteViews} or {@link mWidget} is set, it should be preferred over all
+ * other properties. (An exception is thrown if both are set.)
  * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties.
  * Otherwise, the instance should be treated as a data object.
  *
@@ -133,6 +137,9 @@
     private final AppWidgetProviderInfo mWidget;
 
     @Nullable
+    private final RemoteViews mRemoteViews;
+
+    @Nullable
     private final BaseTemplateData mTemplateData;
 
     public static final int FEATURE_UNDEFINED = 0;
@@ -288,6 +295,7 @@
         this.mSliceUri = in.readTypedObject(Uri.CREATOR);
         this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
         this.mTemplateData = in.readParcelable(/* loader= */null, BaseTemplateData.class);
+        this.mRemoteViews = in.readTypedObject(RemoteViews.CREATOR);
     }
 
     private SmartspaceTarget(String smartspaceTargetId,
@@ -298,7 +306,7 @@
             boolean shouldShowExpanded, String sourceNotificationKey,
             ComponentName componentName, UserHandle userHandle,
             String associatedSmartspaceTargetId, Uri sliceUri,
-            AppWidgetProviderInfo widget, BaseTemplateData templateData) {
+            AppWidgetProviderInfo widget, BaseTemplateData templateData, RemoteViews remoteViews) {
         mSmartspaceTargetId = smartspaceTargetId;
         mHeaderAction = headerAction;
         mBaseAction = baseAction;
@@ -317,6 +325,7 @@
         mSliceUri = sliceUri;
         mWidget = widget;
         mTemplateData = templateData;
+        mRemoteViews = remoteViews;
     }
 
     /**
@@ -461,6 +470,15 @@
     }
 
     /**
+     * Returns the {@link RemoteViews} to show over the target.
+     */
+    @FlaggedApi(Flags.FLAG_REMOTE_VIEWS)
+    @Nullable
+    public RemoteViews getRemoteViews() {
+        return mRemoteViews;
+    }
+
+    /**
      * @see Parcelable.Creator
      */
     @NonNull
@@ -496,6 +514,7 @@
         dest.writeTypedObject(this.mSliceUri, flags);
         dest.writeTypedObject(this.mWidget, flags);
         dest.writeParcelable(this.mTemplateData, flags);
+        dest.writeTypedObject(this.mRemoteViews, flags);
     }
 
     @Override
@@ -524,6 +543,7 @@
                 + ", mSliceUri=" + mSliceUri
                 + ", mWidget=" + mWidget
                 + ", mTemplateData=" + mTemplateData
+                + ", mRemoteViews=" + mRemoteViews
                 + '}';
     }
 
@@ -550,7 +570,8 @@
                 that.mAssociatedSmartspaceTargetId)
                 && Objects.equals(mSliceUri, that.mSliceUri)
                 && Objects.equals(mWidget, that.mWidget)
-                && Objects.equals(mTemplateData, that.mTemplateData);
+                && Objects.equals(mTemplateData, that.mTemplateData)
+                && Objects.equals(mRemoteViews, that.mRemoteViews);
     }
 
     @Override
@@ -558,7 +579,7 @@
         return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
                 mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
                 mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
-                mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
+                mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData, mRemoteViews);
     }
 
     /**
@@ -588,6 +609,8 @@
         private AppWidgetProviderInfo mWidget;
         private BaseTemplateData mTemplateData;
 
+        private RemoteViews mRemoteViews;
+
         /**
          * A builder for {@link SmartspaceTarget}.
          *
@@ -727,9 +750,15 @@
          *
          * <p><b>NOTE: </b> If {@link mWidget} is set, all other @Nullable params should be
          * ignored.
+         *
+         * @throws An {@link IllegalStateException} is thrown if {@link mRemoteViews} is set.
          */
         @NonNull
         public Builder setWidget(@NonNull AppWidgetProviderInfo widget) {
+            if (mRemoteViews != null) {
+                throw new IllegalStateException(
+                        "Widget providers and RemoteViews cannot be used at the same time.");
+            }
             this.mWidget = widget;
             return this;
         }
@@ -745,6 +774,25 @@
         }
 
         /**
+         * Sets the {@link RemoteViews}.
+         *
+         * <p><b>NOTE: </b> If {@link RemoteViews} is set, all other @Nullable params should be
+         * ignored.
+         *
+         * @throws An {@link IllegalStateException} is thrown if {@link mWidget} is set.
+         */
+        @FlaggedApi(Flags.FLAG_REMOTE_VIEWS)
+        @NonNull
+        public Builder setRemoteViews(@NonNull RemoteViews remoteViews) {
+            if (mWidget != null) {
+                throw new IllegalStateException(
+                        "Widget providers and RemoteViews cannot be used at the same time.");
+            }
+            mRemoteViews = remoteViews;
+            return this;
+        }
+
+        /**
          * Builds a new {@link SmartspaceTarget}.
          *
          * @throws IllegalStateException when non null fields are set as null.
@@ -760,7 +808,7 @@
                     mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
                     mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
                     mSourceNotificationKey, mComponentName, mUserHandle,
-                    mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
+                    mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData, mRemoteViews);
         }
     }
 }
diff --git a/core/java/android/app/smartspace/flags.aconfig b/core/java/android/app/smartspace/flags.aconfig
new file mode 100644
index 0000000..12af888
--- /dev/null
+++ b/core/java/android/app/smartspace/flags.aconfig
@@ -0,0 +1,15 @@
+package: "android.app.smartspace.flags"
+
+flag {
+  name: "remote_views"
+  namespace: "sysui_integrations"
+  description: "Flag to enable the FlaggedApi to include RemoteViews in SmartspaceTarget"
+  bug: "300157758"
+}
+
+flag {
+  name: "access_smartspace"
+  namespace: "sysui_integrations"
+  description: "Flag to enable the ACCESS_SMARTSPACE check in SmartspaceManagerService"
+  bug: "297207196"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/core/java/android/app/usage/ParcelableUsageEventList.aidl
similarity index 78%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
copy to core/java/android/app/usage/ParcelableUsageEventList.aidl
index 6d7c576..1652996 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/core/java/android/app/usage/ParcelableUsageEventList.aidl
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.viewmodel
+package android.app.usage;
 
-enum class QSTileLifecycle {
-    ALIVE,
-    DEAD,
-}
+parcelable ParcelableUsageEventList;
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
new file mode 100644
index 0000000..016d97f
--- /dev/null
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.usage;
+
+import android.annotation.NonNull;
+import android.app.usage.UsageEvents.Event;
+import android.content.res.Configuration;
+import android.os.BadParcelableException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a copied version of BaseParceledListSlice with specific
+ * {@link UsageEvents.Event} instance that used to transfer the large
+ * list of {@link UsageEvents.Event} objects across an IPC. Splits
+ * into multiple transactions if needed.
+ *
+ * @see BasedParceledListSlice
+ *
+ * @hide
+ */
+public final class ParcelableUsageEventList implements Parcelable {
+    private static final String TAG = "ParcelableUsageEventList";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_ALL = false;
+
+    private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+
+    private List<Event> mList;
+
+    public ParcelableUsageEventList(List<Event> list) {
+        mList = list;
+    }
+
+    private ParcelableUsageEventList(Parcel in) {
+        final int N = in.readInt();
+        mList = new ArrayList<>();
+        if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+        if (N <= 0) {
+            return;
+        }
+
+        int i = 0;
+        while (i < N) {
+            if (in.readInt() == 0) {
+                break;
+            }
+            mList.add(readEventFromParcel(in));
+            if (DEBUG_ALL) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
+            i++;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Read " + mList.size() + " inline UsageEvents"
+                    + ", total N=" + N + " UsageEvents");
+        }
+        if (i >= N) {
+            return;
+        }
+        final IBinder retriever = in.readStrongBinder();
+        while (i < N) {
+            if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInt(i);
+            try {
+                retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+                reply.readException();
+                int count = 0;
+                while (i < N && reply.readInt() != 0) {
+                    mList.add(readEventFromParcel(reply));
+                    if (DEBUG_ALL) {
+                        Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
+                    }
+                    i++;
+                    count++;
+                }
+                if (DEBUG) Log.d(TAG, "Read extra @" + count + " of " + N);
+            } catch (RemoteException e) {
+                throw new BadParcelableException(
+                    "Failure retrieving array; only received " + i + " of " + N, e);
+            } finally {
+                reply.recycle();
+                data.recycle();
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Finish reading total " + i + " UsageEvents");
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        final int N = mList.size();
+        final int callFlags = flags;
+        dest.writeInt(N);
+        if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+        if (N > 0) {
+            int i = 0;
+            while (i < N && dest.dataSize() < MAX_IPC_SIZE) {
+                dest.writeInt(1);
+
+                final Event event = mList.get(i);
+                writeEventToParcel(event, dest, callFlags);
+
+                if (DEBUG_ALL) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+                i++;
+            }
+            if (i < N) {
+                dest.writeInt(0);
+                Binder retriever = new Binder() {
+                    @Override
+                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                            throws RemoteException {
+                        if (code != FIRST_CALL_TRANSACTION) {
+                            return super.onTransact(code, data, reply, flags);
+                        } else if (mList == null) {
+                            throw new IllegalArgumentException("Attempt to transfer null list, "
+                                + "did transfer finish?");
+                        }
+                        int i = data.readInt();
+
+                        if (DEBUG) {
+                            Log.d(TAG, "Writing more @" + i + " of " + N + " to "
+                                    + Binder.getCallingPid() + ", sender=" + this);
+                        }
+
+                        try {
+                            reply.writeNoException();
+                            int count = 0;
+                            while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+                                reply.writeInt(1);
+
+                                final Event event = mList.get(i);
+                                writeEventToParcel(event, reply, callFlags);
+
+                                if (DEBUG_ALL) {
+                                    Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+                                }
+                                i++;
+                                count++;
+                            }
+                            if (i < N) {
+                                if (DEBUG) {
+                                    Log.d(TAG, "Breaking @" + i + " of " + N
+                                            + "(count = " + count + ")");
+                                }
+                                reply.writeInt(0);
+                            } else {
+                                if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference");
+                                mList = null;
+                            }
+                        } catch (RuntimeException e) {
+                            if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference");
+                            mList = null;
+                            throw e;
+                        }
+                        return true;
+                    }
+                };
+                if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+                dest.writeStrongBinder(retriever);
+            }
+        }
+    }
+
+    public List<Event> getList() {
+        return mList;
+    }
+
+    public static final Parcelable.Creator<ParcelableUsageEventList> CREATOR =
+            new Parcelable.Creator<ParcelableUsageEventList>() {
+                public ParcelableUsageEventList createFromParcel(Parcel in) {
+                    return new ParcelableUsageEventList(in);
+                }
+
+                @Override
+                public ParcelableUsageEventList[] newArray(int size) {
+                    return new ParcelableUsageEventList[size];
+                }
+            };
+
+    private Event readEventFromParcel(Parcel in) {
+        final Event event = new Event();
+        event.mPackage = in.readString();
+        event.mClass = in.readString();
+        event.mInstanceId = in.readInt();
+        event.mTaskRootPackage = in.readString();
+        event.mTaskRootClass = in.readString();
+        event.mEventType = in.readInt();
+        event.mTimeStamp = in.readLong();
+
+        // Fill out the event-dependant fields.
+        event.mConfiguration = null;
+        event.mShortcutId = null;
+        event.mAction = null;
+        event.mContentType = null;
+        event.mContentAnnotations = null;
+        event.mNotificationChannelId = null;
+        event.mLocusId = null;
+
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE -> {
+                event.mConfiguration = Configuration.CREATOR.createFromParcel(in);
+            }
+            case Event.SHORTCUT_INVOCATION -> event.mShortcutId = in.readString();
+            case Event.CHOOSER_ACTION -> {
+                event.mAction = in.readString();
+                event.mContentType = in.readString();
+                event.mContentAnnotations = in.readStringArray();
+            }
+            case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt();
+            case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString();
+            case Event.LOCUS_ID_SET -> event.mLocusId = in.readString();
+        }
+        event.mFlags = in.readInt();
+
+        return event;
+    }
+
+    private void writeEventToParcel(@NonNull Event event, @NonNull Parcel dest, int flags) {
+        dest.writeString(event.mPackage);
+        dest.writeString(event.mClass);
+        dest.writeInt(event.mInstanceId);
+        dest.writeString(event.mTaskRootPackage);
+        dest.writeString(event.mTaskRootClass);
+        dest.writeInt(event.mEventType);
+        dest.writeLong(event.mTimeStamp);
+
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE -> event.mConfiguration.writeToParcel(dest, flags);
+            case Event.SHORTCUT_INVOCATION -> dest.writeString(event.mShortcutId);
+            case Event.CHOOSER_ACTION -> {
+                dest.writeString(event.mAction);
+                dest.writeString(event.mContentType);
+                dest.writeStringArray(event.mContentAnnotations);
+            }
+            case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason);
+            case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId);
+            case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId);
+        }
+        dest.writeInt(event.mFlags);
+    }
+}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3c256ad..c188686 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -714,7 +714,7 @@
     @UnsupportedAppUsage
     private Parcel mParcel = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private final int mEventCount;
+    private int mEventCount;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mIndex = 0;
@@ -735,6 +735,23 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UsageEvents(Parcel in) {
+        if (Flags.useParceledList()) {
+            readUsageEventsFromParcelWithParceledList(in);
+        } else {
+            readUsageEventsFromParcelWithBlob(in);
+        }
+
+        mIncludeTaskRoots = true;
+    }
+
+    private void readUsageEventsFromParcelWithParceledList(Parcel in) {
+        mIndex = in.readInt();
+        mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(),
+            ParcelableUsageEventList.class).getList();
+        mEventCount = mEventsToWrite.size();
+    }
+
+    private void readUsageEventsFromParcelWithBlob(Parcel in) {
         byte[] bytes = in.readBlob();
         Parcel data = Parcel.obtain();
         data.unmarshall(bytes, 0, bytes.length);
@@ -752,7 +769,6 @@
             mParcel.setDataSize(mParcel.dataPosition());
             mParcel.setDataPosition(positionInParcel);
         }
-        mIncludeTaskRoots = true;
     }
 
     /**
@@ -810,6 +826,10 @@
             return false;
         }
 
+        if (Flags.useParceledList()) {
+            return getNextEventFromParceledList(eventOut);
+        }
+
         if (mParcel != null) {
             readEventFromParcel(mParcel, eventOut);
         } else {
@@ -824,6 +844,12 @@
         return true;
     }
 
+    private boolean getNextEventFromParceledList(Event eventOut) {
+        eventOut.copyFrom(mEventsToWrite.get(mIndex));
+        mIndex++;
+        return true;
+    }
+
     /**
      * Resets the collection so that it can be iterated over from the beginning.
      *
@@ -968,7 +994,7 @@
             case Event.CHOOSER_ACTION:
                 eventOut.mAction = p.readString();
                 eventOut.mContentType = p.readString();
-                eventOut.mContentAnnotations = p.createStringArray();
+                eventOut.mContentAnnotations = p.readStringArray();
                 break;
             case Event.STANDBY_BUCKET_CHANGED:
                 eventOut.mBucketAndReason = p.readInt();
@@ -990,6 +1016,19 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        if (Flags.useParceledList()) {
+            writeUsageEventsToParcelWithParceledList(dest, flags);
+        } else {
+            writeUsageEventsToParcelWithBlob(dest, flags);
+        }
+    }
+
+    private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) {
+        dest.writeInt(mIndex);
+        dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags);
+    }
+
+    private void writeUsageEventsToParcelWithBlob(Parcel dest, int flags) {
         Parcel data = Parcel.obtain();
         data.writeInt(mEventCount);
         data.writeInt(mIndex);
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index afe87de..0b8e29f 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -2,8 +2,29 @@
 
 flag {
     name: "user_interaction_type_api"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for user interaction event report/query API"
     bug: "296061232"
 }
 
+flag {
+    name: "report_usage_stats_permission"
+    namespace: "backstage_power"
+    description: "Feature flag for the new REPORT_USAGE_STATS permission."
+    bug: "296056771"
+}
+
+flag {
+    name: "use_dedicated_handler_thread"
+    namespace: "backstage_power"
+    description: "Flag to use a dedicated thread for usage event process"
+    is_fixed_read_only: true
+    bug: "299336442"
+}
+
+flag {
+    name: "use_parceled_list"
+    namespace: "backstage_power"
+    description: "Flag for parcelable usage event list"
+    bug: "301254110"
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 083fa00..161fa79 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -15,6 +15,7 @@
  */
 package android.companion;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -143,6 +144,7 @@
      * @return the tag of this association.
      * @see CompanionDeviceManager#setAssociationTag(int, String)
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @Nullable
     public String getTag() {
         return mTag;
@@ -204,9 +206,8 @@
     /**
      * @return whether the association is managed by the companion application it belongs to.
      * @see AssociationRequest.Builder#setSelfManaged(boolean)
-     * @hide
      */
-    @SystemApi
+    @SuppressLint("UnflaggedApi") // promoting from @SystemApi
     public boolean isSelfManaged() {
         return mSelfManaged;
     }
@@ -412,6 +413,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER)
     @TestApi
     public static final class Builder {
         private final int mId;
@@ -457,6 +459,7 @@
         }
 
         /** @hide */
+        @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
         @TestApi
         @NonNull
         public Builder setTag(@Nullable String tag) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a84845a..70811bb 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,6 +23,7 @@
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -216,12 +217,14 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @TestApi public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
     /**
      * Message header assigned to the remote authentication handshakes.
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 0x63827765; // ?RMA
     /**
@@ -229,6 +232,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
     /**
@@ -236,6 +240,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
 
@@ -873,6 +878,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public interface OnTransportsChangedListener {
         /**
@@ -892,6 +898,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnTransportsChangedListener(
@@ -913,6 +920,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnTransportsChangedListener(
@@ -934,6 +942,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
@@ -951,6 +960,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public interface OnMessageReceivedListener {
         /**
@@ -964,6 +974,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnMessageReceivedListener(
@@ -983,6 +994,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnMessageReceivedListener(int messageType,
@@ -1423,6 +1435,7 @@
      *                          of the companion device recorded by CompanionDeviceManager
      * @param tag the tag of this association
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void setAssociationTag(int associationId, @NonNull String tag) {
         Objects.requireNonNull(tag, "tag cannot be null");
@@ -1447,6 +1460,7 @@
      *                          of the companion device recorded by CompanionDeviceManager
      * @see CompanionDeviceManager#setAssociationTag(int, String)
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void clearAssociationTag(int associationId) {
         try {
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 03e75e9..c99a457 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -17,6 +17,7 @@
 
 package android.companion;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -140,38 +141,44 @@
      * 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 {@link AssociationInfo#isSelfManaged() self-managed} device will
-     * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
-     * device has appeared on its own.
+     * 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 {@link AssociationInfo#isSelfManaged() self-managed} device will
-     * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
-     * device has disappeared on its own.
+     * 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();
@@ -348,6 +355,7 @@
      * @param associationInfo A record for the companion device.
      * @param event Associated companion device's event.
      */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
     @MainThread
     public void onDeviceEvent(@NonNull AssociationInfo associationInfo,
             @DeviceEvent int event) {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
new file mode 100644
index 0000000..6e33dff
--- /dev/null
+++ b/core/java/android/companion/flags.aconfig
@@ -0,0 +1,29 @@
+package: "android.companion"
+
+flag {
+    name: "new_association_builder"
+    namespace: "companion"
+    description: "Controls if the new Builder is exposed to test apis."
+    bug: "296251481"
+}
+
+flag {
+    name: "companion_transport_apis"
+    namespace: "companion"
+    description: "Grants access to the companion transport apis."
+    bug: "288297505"
+}
+
+flag {
+    name: "association_tag"
+    namespace: "companion"
+    description: "Enable Association tag APIs "
+    bug: "289241123"
+}
+
+flag {
+    name: "device_presence"
+    namespace: "companion"
+    description: "Enable device presence APIs"
+    bug: "283000075"
+}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index ce883cd..93a3e78 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -33,9 +33,6 @@
  *
  * <p>Read-only device representation exposing the properties of an existing virtual device.
  *
- * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
- * by the virtual device creator and allows them to manage the device.
- *
  * @see VirtualDeviceManager#registerVirtualDeviceListener
  */
 public final class VirtualDevice implements Parcelable {
@@ -113,14 +110,13 @@
      * <p class="note">This identifier may not be unique across virtual devices, in case there are
      * more than one virtual devices corresponding to the same physical device.
      */
+    @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
     public @Nullable String getPersistentDeviceId() {
         return mPersistentId;
     }
 
     /**
      * Returns the name of the virtual device (optionally) provided during its creation.
-     *
-     * @see VirtualDeviceParams.Builder#setName(String)
      */
     public @Nullable String getName() {
         return mName;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 39800f7..2569366 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -476,6 +476,7 @@
         /**
          * Returns the persistent ID of this virtual device.
          */
+        @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
         public @Nullable String getPersistentDeviceId() {
             return mVirtualDeviceInternal.getPersistentDeviceId();
         }
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index eaa1792..14c7997 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -115,6 +115,11 @@
         parcel.writeStrongBinder(mToken);
     }
 
+    @Override
+    public String toString() {
+        return "VirtualSensor{ mType=" + mType + ", mName='" + mName + "' }";
+    }
+
     /**
      * Send a sensor event to the system.
      */
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index bf78dd0..b9451a7 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -46,15 +46,15 @@
  * <pre>
  * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
  * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
- *     @Override
+ *     {@literal @}Override
  *     public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
  *         writer.addChannel(channelHandle, sharedMemory);
  *     }
- *     @Override
+ *     {@literal @}Override
  *     public void onDirectChannelDestroyed(int channelHandle);
  *         writer.removeChannel(channelHandle);
  *     }
- *     @Override
+ *     {@literal @}Override
  *     public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
  *             int reportToken)
  *         if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bfc1eec..4b2cee6 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -155,6 +155,7 @@
 
     /** @hide */
     @TestApi
+    @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR)
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
             @Nullable String[] renouncedPermissions,
@@ -730,7 +731,7 @@
         /**
          * The next app to receive the permission protected data.
          *
-         * @deprecated Use {@link setNextAttributionSource} instead.
+         * @deprecated Use {@link #setNextAttributionSource} instead.
          */
         @Deprecated
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
@@ -744,6 +745,7 @@
         /**
          * The next app to receive the permission protected data.
          */
+        @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
         public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 36e0529..3fbcd70 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -30,6 +30,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Content capture options for a given package.
@@ -119,7 +124,10 @@
                 /* enableReceiver= */ false,
                 new ContentProtectionOptions(
                         /* enableReceiver= */ false,
-                        /* bufferSize= */ 0),
+                        /* bufferSize= */ 0,
+                        /* requiredGroups= */ Collections.emptyList(),
+                        /* optionalGroups= */ Collections.emptyList(),
+                        /* optionalGroupsThreshold= */ 0),
                 /* whitelistedComponents= */ null);
     }
 
@@ -141,9 +149,7 @@
                 logHistorySize,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
-                new ContentProtectionOptions(
-                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
-                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
+                new ContentProtectionOptions(),
                 whitelistedComponents);
     }
 
@@ -183,9 +189,7 @@
                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
-                new ContentProtectionOptions(
-                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
-                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
+                new ContentProtectionOptions(),
                 whitelistedComponents);
     }
 
@@ -386,9 +390,58 @@
          */
         public final int bufferSize;
 
-        public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
+        /**
+         * The list of required groups of strings to match.
+         *
+         * @hide
+         */
+        @NonNull public final List<List<String>> requiredGroups;
+
+        /**
+         * The list of optional groups of strings to match.
+         *
+         * @hide
+         */
+        @NonNull public final List<List<String>> optionalGroups;
+
+        /**
+         * The minimal number of optional groups that have to be matched. This is the threshold
+         * value and comparison is done with greater than or equals.
+         *
+         * @hide
+         */
+        public final int optionalGroupsThreshold;
+
+        /**
+         * Empty constructor with default values.
+         *
+         * @hide
+         */
+        public ContentProtectionOptions() {
+            this(
+                    ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
+        }
+
+        /**
+         * Full primary constructor.
+         *
+         * @hide
+         */
+        public ContentProtectionOptions(
+                boolean enableReceiver,
+                int bufferSize,
+                @NonNull List<List<String>> requiredGroups,
+                @NonNull List<List<String>> optionalGroups,
+                int optionalGroupsThreshold) {
             this.enableReceiver = enableReceiver;
             this.bufferSize = bufferSize;
+            this.requiredGroups = requiredGroups;
+            this.optionalGroups = optionalGroups;
+            this.optionalGroupsThreshold = optionalGroupsThreshold;
         }
 
         @Override
@@ -398,7 +451,14 @@
                     .append("enableReceiver=")
                     .append(enableReceiver)
                     .append(", bufferSize=")
-                    .append(bufferSize);
+                    .append(bufferSize)
+                    .append(", requiredGroupsSize=")
+                    .append(requiredGroups.size())
+                    .append(", optionalGroupsSize=")
+                    .append(optionalGroups.size())
+                    .append(", optionalGroupsThreshold=")
+                    .append(optionalGroupsThreshold);
+
             return stringBuilder.append(']').toString();
         }
 
@@ -407,17 +467,50 @@
             pw.print(enableReceiver);
             pw.print(", bufferSize=");
             pw.print(bufferSize);
+            pw.print(", requiredGroupsSize=");
+            pw.print(requiredGroups.size());
+            pw.print(", optionalGroupsSize=");
+            pw.print(optionalGroups.size());
+            pw.print(", optionalGroupsThreshold=");
+            pw.print(optionalGroupsThreshold);
         }
 
-        private void writeToParcel(Parcel parcel) {
+        private void writeToParcel(@NonNull Parcel parcel) {
             parcel.writeBoolean(enableReceiver);
             parcel.writeInt(bufferSize);
+            writeGroupsToParcel(requiredGroups, parcel);
+            writeGroupsToParcel(optionalGroups, parcel);
+            parcel.writeInt(optionalGroupsThreshold);
         }
 
-        private static ContentProtectionOptions createFromParcel(Parcel parcel) {
+        @NonNull
+        private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) {
             boolean enableReceiver = parcel.readBoolean();
             int bufferSize = parcel.readInt();
-            return new ContentProtectionOptions(enableReceiver, bufferSize);
+            List<List<String>> requiredGroups = createGroupsFromParcel(parcel);
+            List<List<String>> optionalGroups = createGroupsFromParcel(parcel);
+            int optionalGroupsThreshold = parcel.readInt();
+            return new ContentProtectionOptions(
+                    enableReceiver,
+                    bufferSize,
+                    requiredGroups,
+                    optionalGroups,
+                    optionalGroupsThreshold);
+        }
+
+        private static void writeGroupsToParcel(
+                @NonNull List<List<String>> groups, @NonNull Parcel parcel) {
+            parcel.writeInt(groups.size());
+            groups.forEach(parcel::writeStringList);
+        }
+
+        @NonNull
+        private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) {
+            int size = parcel.readInt();
+            return IntStream.range(0, size)
+                    .mapToObj(i -> new ArrayList<String>())
+                    .peek(parcel::readStringList)
+                    .collect(Collectors.toUnmodifiableList());
         }
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b6a98a5..1c917ee 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -23,6 +23,7 @@
 import android.annotation.ColorRes;
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
@@ -323,7 +324,7 @@
             // Make sure no flag uses the sign bit (most significant bit) of the long integer,
             // to avoid future confusion.
             BIND_BYPASS_USER_NETWORK_RESTRICTIONS,
-            BIND_FILTER_OUT_QUARANTINED_COMPONENTS,
+            BIND_MATCH_QUARANTINED_COMPONENTS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BindServiceFlagsLongBits {}
@@ -703,7 +704,7 @@
      *
      * @hide
      */
-    public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
+    public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
 
 
     /**
@@ -860,12 +861,23 @@
      * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
      * appropriate in the future; this will not be removed for you.
      * <p>
-     * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+     * After {@link Build.VERSION_CODES#S}, registering the ComponentCallbacks to Context created
      * via {@link #createWindowContext(int, Bundle)} or
      * {@link #createWindowContext(Display, int, Bundle)} will receive
      * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
      * than its base application. It is helpful if you want to handle UI components that
      * associated with the Window Context when the Window Context has configuration changes.</p>
+     * <p>
+     * After {@link Build.VERSION_CODES#TIRAMISU}, registering the ComponentCallbacks to
+     * {@link Activity} context will receive
+     * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from
+     * {@link Activity#onConfigurationChanged(Configuration)} rather than its base application.</p>
+     * <p>
+     * After {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, registering the ComponentCallbacks to
+     * {@link android.inputmethodservice.InputMethodService} will receive
+     * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from InputmethodService
+     * rather than its base application. It is helpful if you want to handle UI components when the
+     * IME has configuration changes.</p>
      *
      * @param callback The interface to call.  This can be either a
      * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
@@ -4357,7 +4369,6 @@
      * @see android.telephony.CarrierConfigManager
      * @see #EUICC_SERVICE
      * @see android.telephony.euicc.EuiccManager
-     * @see android.telephony.MmsManager
      * @see #INPUT_METHOD_SERVICE
      * @see android.view.inputmethod.InputMethodManager
      * @see #UI_MODE_SERVICE
@@ -4774,6 +4785,7 @@
      * @see android.net.thread.ThreadNetworkManager
      * @hide
      */
+    @FlaggedApi("com.android.net.thread.flags.thread_enabled")
     @SystemApi
     public static final String THREAD_NETWORK_SERVICE = "thread_network";
 
@@ -6371,7 +6383,6 @@
      * @see android.remoteauth.RemoteAuthManager
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String REMOTE_AUTH_SERVICE = "remote_auth";
 
     /**
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 988a9c0..b844d35 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -16,7 +16,10 @@
 
 package android.content;
 
+import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -102,6 +105,7 @@
      * registered.
      */
     @NonNull
+    @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
     public boolean shouldRegisterAttributionSource() {
         return mShouldRegisterAttributionSource;
     }
@@ -179,6 +183,7 @@
          *                       created should be registered.
          */
         @NonNull
+        @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
         public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
             mShouldRegisterAttributionSource = shouldRegister;
             return this;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 44a9acd..ffc4805 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -64,6 +64,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCommand;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.provider.ContactsContract.QuickContact;
@@ -2790,6 +2791,22 @@
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
+
+    /**
+     * Broadcast Action: An application package that was previously in the stopped state has been
+     * started and is no longer considered stopped.
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
+     *          elapsed realtime} of when the package was unstopped.
+     * </ul>
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED)
+    public static final String ACTION_PACKAGE_UNSTOPPED = "android.intent.action.PACKAGE_UNSTOPPED";
+
     /**
      * Broadcast Action: Sent to the system rollback manager when a package
      * needs to have rollback enabled.
@@ -2855,9 +2872,15 @@
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
+     * <p>
+     * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+     * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
+     * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
+     * </p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+
     /**
      * Broadcast Action: The user has cleared the data of a package.  This should
      * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
@@ -3915,6 +3938,8 @@
      * {@link #ACTION_BOOT_COMPLETED} is sent.  This is sent as a foreground
      * broadcast, since it is part of a visible user interaction; be as quick
      * as possible when handling it.
+     *
+     * <p><b>Note:</b> This broadcast is not sent to the system user.
      */
     public static final String ACTION_USER_INITIALIZE =
             "android.intent.action.USER_INITIALIZE";
@@ -4166,7 +4191,7 @@
      * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
-     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * generic broadcast for all profile users.
      */
     @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String ACTION_PROFILE_AVAILABLE =
@@ -4180,7 +4205,7 @@
      * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
-     * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * a generic broadcast for all profile users.
      */
     @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String ACTION_PROFILE_UNAVAILABLE =
@@ -4208,7 +4233,7 @@
      * that was removed.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a
-     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * generic broadcast for all profile users.
      * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a
      * managed user is removed.
      *
@@ -4228,7 +4253,7 @@
      * that was added.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a
-     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * generic broadcast for all profile users.
      * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a
      * managed user is added.
      *
@@ -4267,6 +4292,14 @@
             "com.android.intent.action.SHOW_BRIGHTNESS_DIALOG";
 
     /**
+     * Intent Extra: holds boolean that determines whether brightness dialog is full width when
+     * in landscape mode.
+     * @hide
+     */
+    public static final String EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH =
+            "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
+
+    /**
      * Activity Action: Shows the contrast setting dialog.
      * @hide
      */
@@ -5309,6 +5342,7 @@
      * @hide
      */
     @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
 
     // ---------------------------------------------------------------------
@@ -6306,6 +6340,7 @@
      * the package is being archived. Either by removing the existing APK, or by installing
      * a package without an APK.
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public static final String EXTRA_ARCHIVAL = "android.intent.extra.ARCHIVAL";
 
     /**
@@ -6561,8 +6596,8 @@
             = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
 
     /**
-     * Optional extra specifying a time in milliseconds since the Epoch. The value must be
-     * non-negative.
+     * Optional extra specifying a time in milliseconds. The timebase depends on the Intent
+     * including this extra. The value must be non-negative.
      * <p>
      * Type: long
      * </p>
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8..df2d7e7 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,6 +16,7 @@
 
 package android.content.om;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -546,6 +547,7 @@
      * @param configuration The string representation of the config this overlay is enabled for
      */
     @NonNull
+    @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO)
     public void setResourceValue(
             @NonNull String resourceName,
             @NonNull AssetFileDescriptor value,
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
index f256372..30875aa 100644
--- a/core/java/android/content/om/OverlayIdentifier.java
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -39,7 +39,6 @@
  * -->
  *
  * @see OverlayInfo#getOverlayIdentifier()
- * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
  */
 @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
         genEqualsHashCode = true, genToString = false)
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index ff1c088..2e89856 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -385,7 +385,6 @@
      * <p>The return value of this function can be used to unregister the related overlay.
      *
      * @return an identifier representing the current overlay.
-     * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
      */
     @Override
     @NonNull
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 0fcc72a1..ed965b3 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -50,7 +50,7 @@
  *   <li>register overlays
  *   <li>unregister overlays
  *   <li>execute multiple operations in one commitment by calling {@link
- *       OverlayManagerTransaction#commit()}
+ *       #commit(OverlayManagerTransaction)}
  * </ul>
  *
  * @see OverlayManagerTransaction
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 036a4eb..323592c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1185,8 +1185,8 @@
      * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
      * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
      * through
-     * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
-     * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+     * {@code android.view.ViewRootImpl#getWindowVisibleDisplayFrame},
+     * {@code android.view.ViewRootImpl#getDisplayFrame} respectively.
      *
      * <p>Some applications assume that they occupy the whole screen and therefore use the display
      * coordinates in their calculations as if an activity is  positioned in the top-left corner of
@@ -1204,12 +1204,15 @@
     /**
      * This change id is the gatekeeper for all treatments that force a given min aspect ratio.
      * Enabling this change will allow the following min aspect ratio treatments to be applied:
-     * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
-     * OVERRIDE_MIN_ASPECT_RATIO_LARGE
+     * <ul>
+     *  <li>OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
+     *  <li>OVERRIDE_MIN_ASPECT_RATIO_LARGE
+     * </ul>
      *
      * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest
      * will be overridden to the largest enabled aspect ratio treatment unless the app's manifest
-     * value is higher.
+     * value is higher. By default, this will only apply to activities with fixed portrait
+     * orientation if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY is not explicitly disabled.
      * @hide
      */
     @ChangeId
diff --git a/core/java/android/content/pm/ArchivedActivity.java b/core/java/android/content/pm/ArchivedActivity.java
new file mode 100644
index 0000000..9e49c9e
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedActivity.java
@@ -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 android.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.util.DataClass;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+
+@DataClass(genBuilder = false, genConstructor = false, genSetters = true)
+@FlaggedApi(Flags.FLAG_ARCHIVING)
+public final class ArchivedActivity {
+    /** The label for the activity. */
+    private @NonNull CharSequence mLabel;
+    /** The component name of this activity. */
+    private @NonNull ComponentName mComponentName;
+    /**
+     * Icon of the activity in the app's locale. if null then the default icon would be shown in the
+     * launcher.
+     */
+    private @Nullable Drawable mIcon;
+    /** Monochrome icon, if defined, of the activity. */
+    private @Nullable Drawable mMonochromeIcon;
+
+    public ArchivedActivity(@NonNull CharSequence label, @NonNull ComponentName componentName) {
+        Objects.requireNonNull(label);
+        Objects.requireNonNull(componentName);
+        mLabel = label;
+        mComponentName = componentName;
+    }
+
+    /* @hide */
+    ArchivedActivity(@NonNull ArchivedActivityParcel parcel) {
+        mLabel = parcel.title;
+        mComponentName = parcel.originalComponentName;
+        mIcon = drawableFromCompressedBitmap(parcel.iconBitmap);
+        mMonochromeIcon = drawableFromCompressedBitmap(parcel.monochromeIconBitmap);
+    }
+
+    /* @hide */
+    @NonNull ArchivedActivityParcel getParcel() {
+        var parcel = new ArchivedActivityParcel();
+        parcel.title = mLabel.toString();
+        parcel.originalComponentName = mComponentName;
+        parcel.iconBitmap = mIcon == null ? null :
+                bytesFromBitmap(drawableToBitmap(mIcon));
+        parcel.monochromeIconBitmap = mMonochromeIcon == null ? null :
+                bytesFromBitmap(drawableToBitmap(mMonochromeIcon));
+        return parcel;
+    }
+
+    /**
+     * Convert a generic drawable into a bitmap.
+     * @hide
+     */
+    public static Bitmap drawableToBitmap(Drawable drawable) {
+        return drawableToBitmap(drawable, /* iconSize= */ 0);
+    }
+
+    /**
+     * Same as above, but scale the resulting image to fit iconSize.
+     * @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);
+        } else {
+            bitmap =
+                    Bitmap.createBitmap(
+                            drawable.getIntrinsicWidth(),
+                            drawable.getIntrinsicHeight(),
+                            Bitmap.Config.ARGB_8888);
+        }
+        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) {
+            var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
+            if (scaledBitmap != bitmap) {
+                bitmap.recycle();
+            }
+            return scaledBitmap;
+        }
+        return bitmap;
+    }
+
+    /**
+     * Compress bitmap to PNG format.
+     * @hide
+     */
+    public static byte[] bytesFromBitmap(Bitmap bitmap) {
+        if (bitmap == null) {
+            return null;
+        }
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(
+                bitmap.getByteCount())) {
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+            return baos.toByteArray();
+        } catch (IOException ignored) {
+            return null;
+        }
+    }
+
+    private static Drawable drawableFromCompressedBitmap(byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+        return new BitmapDrawable(null /*res*/, new ByteArrayInputStream(bytes));
+    }
+
+
+
+    // 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/content/pm/ArchivedActivity.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * The label for the activity.
+     */
+    @DataClass.Generated.Member
+    public @NonNull CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * The component name of this activity.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * Icon of the activity in the app's locale. if null then the default icon would be shown in the
+     * launcher.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Drawable getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Monochrome icon, if defined, of the activity.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Drawable getMonochromeIcon() {
+        return mMonochromeIcon;
+    }
+
+    /**
+     * The label for the activity.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedActivity setLabel(@NonNull CharSequence value) {
+        mLabel = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLabel);
+        return this;
+    }
+
+    /**
+     * The component name of this activity.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedActivity setComponentName(@NonNull ComponentName value) {
+        mComponentName = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mComponentName);
+        return this;
+    }
+
+    /**
+     * Icon of the activity in the app's locale. if null then the default icon would be shown in the
+     * launcher.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedActivity setIcon(@NonNull Drawable value) {
+        mIcon = value;
+        return this;
+    }
+
+    /**
+     * Monochrome icon, if defined, of the activity.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedActivity setMonochromeIcon(@NonNull Drawable value) {
+        mMonochromeIcon = value;
+        return this;
+    }
+
+    @DataClass.Generated(
+            time = 1698173429911L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivity.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 ArchivedActivity extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
index 7ab7ed1..74953ff 100644
--- a/core/java/android/content/pm/ArchivedActivityParcel.aidl
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -16,9 +16,12 @@
 
 package android.content.pm;
 
+import android.content.ComponentName;
+
 /** @hide */
 parcelable ArchivedActivityParcel {
     String title;
+    ComponentName originalComponentName;
     // PNG compressed bitmaps.
     byte[] iconBitmap;
     byte[] monochromeIconBitmap;
diff --git a/core/java/android/content/pm/ArchivedPackage.java b/core/java/android/content/pm/ArchivedPackage.java
new file mode 100644
index 0000000..42795db
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedPackage.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@DataClass(genBuilder = false, genConstructor = false, genSetters = true)
+@FlaggedApi(Flags.FLAG_ARCHIVING)
+public final class ArchivedPackage {
+    /** Name of the package as used to identify it in the system */
+    private @NonNull String mPackageName;
+    /** Signing certificates used to sign the package. */
+    private @NonNull SigningInfo mSigningInfo;
+    /**
+     * The version number of the package, as specified by the &lt;manifest&gt;tag's
+     * {@link android.R.styleable#AndroidManifest_versionCode versionCode} attribute.
+     */
+    private int mVersionCode = 0;
+    /**
+     * The major version number of the package, as specified by the &lt;manifest&gt;tag's
+     * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute.
+     */
+    private int mVersionCodeMajor = 0;
+    /**
+     * This is the SDK version number that the application is targeting, as specified by the
+     * &lt;manifest&gt; tag's {@link android.R.styleable#AndroidManifestUsesSdk_targetSdkVersion}
+     * attribute.
+     */
+    private int mTargetSdkVersion = 0;
+    /**
+     * Package data will default to device protected storage. Specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage}
+     * attribute.
+     */
+    private @Nullable String mDefaultToDeviceProtectedStorage;
+    /**
+     * If {@code true} this app would like to run under the legacy storage model. Specified by the
+     * &lt;manifest&gt; tag's
+     * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage}
+     * attribute.
+     */
+    private @Nullable String mRequestLegacyExternalStorage;
+    /**
+     * If {@code true} the user is prompted to keep the app's data on uninstall. Specified by the
+     * &lt;manifest&gt; tag's
+     * {@link android.R.styleable#AndroidManifestApplication_hasFragileUserData} attribute.
+     */
+    private @Nullable String mUserDataFragile;
+    /**
+     * List of the package's activities that specify {@link Intent#ACTION_MAIN} and
+     * {@link Intent#CATEGORY_LAUNCHER}.
+     * @see LauncherApps#getActivityList
+     */
+    private @NonNull List<ArchivedActivity> mLauncherActivities;
+
+    public ArchivedPackage(@NonNull String packageName, @NonNull SigningInfo signingInfo,
+            @NonNull List<ArchivedActivity> launcherActivities) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(signingInfo);
+        Objects.requireNonNull(launcherActivities);
+        this.mPackageName = packageName;
+        this.mSigningInfo = signingInfo;
+        this.mLauncherActivities = launcherActivities;
+    }
+
+    /**
+     * Constructs the archived package from parcel.
+     * @hide
+     */
+    public ArchivedPackage(@NonNull ArchivedPackageParcel parcel) {
+        mPackageName = parcel.packageName;
+        mSigningInfo = new SigningInfo(parcel.signingDetails);
+        mVersionCode = parcel.versionCode;
+        mVersionCodeMajor = parcel.versionCodeMajor;
+        mTargetSdkVersion = parcel.targetSdkVersion;
+        mDefaultToDeviceProtectedStorage = parcel.defaultToDeviceProtectedStorage;
+        mRequestLegacyExternalStorage = parcel.requestLegacyExternalStorage;
+        mUserDataFragile = parcel.userDataFragile;
+        mLauncherActivities = new ArrayList<>();
+        if (parcel.archivedActivities != null) {
+            for (var activityParcel : parcel.archivedActivities) {
+                mLauncherActivities.add(new ArchivedActivity(activityParcel));
+            }
+        }
+    }
+
+    /* @hide */
+    ArchivedPackageParcel getParcel() {
+        var parcel = new ArchivedPackageParcel();
+        parcel.packageName = mPackageName;
+        parcel.signingDetails = mSigningInfo.getSigningDetails();
+        parcel.versionCode = mVersionCode;
+        parcel.versionCodeMajor = mVersionCodeMajor;
+        parcel.targetSdkVersion = mTargetSdkVersion;
+        parcel.defaultToDeviceProtectedStorage = mDefaultToDeviceProtectedStorage;
+        parcel.requestLegacyExternalStorage = mRequestLegacyExternalStorage;
+        parcel.userDataFragile = mUserDataFragile;
+
+        parcel.archivedActivities = new ArchivedActivityParcel[mLauncherActivities.size()];
+        for (int i = 0, size = parcel.archivedActivities.length; i < size; ++i) {
+            parcel.archivedActivities[i] = mLauncherActivities.get(i).getParcel();
+        }
+
+        return parcel;
+    }
+
+
+
+    // 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/content/pm/ArchivedPackage.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Name of the package as used to identify it in the system
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Signing certificates used to sign the package.
+     */
+    @DataClass.Generated.Member
+    public @NonNull SigningInfo getSigningInfo() {
+        return mSigningInfo;
+    }
+
+    /**
+     * The version number of the package, as specified by the &lt;manifest&gt;tag's
+     * {@link android.R.styleable#AndroidManifest_versionCode versionCode} attribute.
+     */
+    @DataClass.Generated.Member
+    public int getVersionCode() {
+        return mVersionCode;
+    }
+
+    /**
+     * The major version number of the package, as specified by the &lt;manifest&gt;tag's
+     * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute.
+     */
+    @DataClass.Generated.Member
+    public int getVersionCodeMajor() {
+        return mVersionCodeMajor;
+    }
+
+    /**
+     * This is the SDK version number that the application is targeting, as specified by the
+     * &lt;manifest&gt; tag's {@link android.R.styleable#AndroidManifestUsesSdk_targetSdkVersion}
+     * attribute.
+     */
+    @DataClass.Generated.Member
+    public int getTargetSdkVersion() {
+        return mTargetSdkVersion;
+    }
+
+    /**
+     * Package data will default to device protected storage. Specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage}
+     * attribute.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getDefaultToDeviceProtectedStorage() {
+        return mDefaultToDeviceProtectedStorage;
+    }
+
+    /**
+     * If {@code true} this app would like to run under the legacy storage model. Specified by the
+     * &lt;manifest&gt; tag's
+     * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage}
+     * attribute.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getRequestLegacyExternalStorage() {
+        return mRequestLegacyExternalStorage;
+    }
+
+    /**
+     * If {@code true} the user is prompted to keep the app's data on uninstall. Specified by the
+     * &lt;manifest&gt; tag's
+     * {@link android.R.styleable#AndroidManifestApplication_hasFragileUserData} attribute.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getUserDataFragile() {
+        return mUserDataFragile;
+    }
+
+    /**
+     * List of the package's activities that specify {@link Intent#ACTION_MAIN} and
+     * {@link Intent#CATEGORY_LAUNCHER}.
+     *
+     * @see LauncherApps#getActivityList
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<ArchivedActivity> getLauncherActivities() {
+        return mLauncherActivities;
+    }
+
+    /**
+     * Name of the package as used to identify it in the system
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setPackageName(@NonNull String value) {
+        mPackageName = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        return this;
+    }
+
+    /**
+     * Signing certificates used to sign the package.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setSigningInfo(@NonNull SigningInfo value) {
+        mSigningInfo = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSigningInfo);
+        return this;
+    }
+
+    /**
+     * The version number of the package, as specified by the &lt;manifest&gt;tag's
+     * {@link android.R.styleable#AndroidManifest_versionCode versionCode} attribute.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setVersionCode( int value) {
+        mVersionCode = value;
+        return this;
+    }
+
+    /**
+     * The major version number of the package, as specified by the &lt;manifest&gt;tag's
+     * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setVersionCodeMajor( int value) {
+        mVersionCodeMajor = value;
+        return this;
+    }
+
+    /**
+     * This is the SDK version number that the application is targeting, as specified by the
+     * &lt;manifest&gt; tag's {@link android.R.styleable#AndroidManifestUsesSdk_targetSdkVersion}
+     * attribute.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setTargetSdkVersion( int value) {
+        mTargetSdkVersion = value;
+        return this;
+    }
+
+    /**
+     * Package data will default to device protected storage. Specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage}
+     * attribute.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setDefaultToDeviceProtectedStorage(@NonNull String value) {
+        mDefaultToDeviceProtectedStorage = value;
+        return this;
+    }
+
+    /**
+     * If {@code true} this app would like to run under the legacy storage model. Specified by the
+     * &lt;manifest&gt; tag's
+     * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage}
+     * attribute.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setRequestLegacyExternalStorage(@NonNull String value) {
+        mRequestLegacyExternalStorage = value;
+        return this;
+    }
+
+    /**
+     * If {@code true} the user is prompted to keep the app's data on uninstall. Specified by the
+     * &lt;manifest&gt; tag's
+     * {@link android.R.styleable#AndroidManifestApplication_hasFragileUserData} attribute.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setUserDataFragile(@NonNull String value) {
+        mUserDataFragile = value;
+        return this;
+    }
+
+    /**
+     * List of the package's activities that specify {@link Intent#ACTION_MAIN} and
+     * {@link Intent#CATEGORY_LAUNCHER}.
+     *
+     * @see LauncherApps#getActivityList
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArchivedPackage setLauncherActivities(@NonNull List<ArchivedActivity> value) {
+        mLauncherActivities = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLauncherActivities);
+        return this;
+    }
+
+    @DataClass.Generated(
+            time = 1697824890503L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedPackage.java",
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate  int mVersionCode\nprivate  int mVersionCodeMajor\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable java.lang.String mDefaultToDeviceProtectedStorage\nprivate @android.annotation.Nullable java.lang.String mRequestLegacyExternalStorage\nprivate @android.annotation.Nullable java.lang.String mUserDataFragile\nprivate @android.annotation.NonNull java.util.List<android.content.pm.ArchivedActivity> mLauncherActivities\n  android.content.pm.ArchivedPackageParcel getParcel()\nclass ArchivedPackage extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 563ed7d..e9f419e 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.LocusId;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.LauncherActivityInfoInternal;
@@ -62,6 +63,7 @@
             in Bundle opts, in UserHandle user);
     PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
             in UserHandle user);
+    LauncherUserInfo getLauncherUserInfo(in UserHandle user);
     void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index edb07ce..59ed045 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
@@ -82,4 +83,11 @@
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)")
+    void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel,
+            in PackageInstaller.SessionParams params,
+            in IntentSender statusReceiver,
+            String installerPackageName, in UserHandle userHandle);
+
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index aca88d6..9926415 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -308,6 +308,8 @@
 
     boolean isPackageQuarantinedForUser(String packageName, int userId);
 
+    boolean isPackageStoppedForUser(String packageName, int userId);
+
     Bundle getSuspendedPackageAppExtras(String packageName, int userId);
 
     /**
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index dbaa4c9..0cd4358 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -55,6 +56,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Flags;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -776,6 +778,28 @@
     }
 
     /**
+     * Returns information related to a user which is useful for displaying UI elements
+     * to distinguish it from other users (eg, badges). Only system launchers should
+     * call this API.
+     *
+     * @param userHandle user handle of the user for which LauncherUserInfo is requested
+     * @return the LauncherUserInfo object related to the user specified.
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) {
+        if (DEBUG) {
+            Log.i(TAG, "getLauncherUserInfo " + userHandle);
+        }
+        try {
+            return mService.getLauncherUserInfo(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
      * returns null.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/core/java/android/content/pm/LauncherUserInfo.aidl
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
rename to core/java/android/content/pm/LauncherUserInfo.aidl
index 6d7c576..f875f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/core/java/android/content/pm/LauncherUserInfo.aidl
@@ -11,12 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.systemui.qs.tiles.viewmodel
+package android.content.pm;
 
-enum class QSTileLifecycle {
-    ALIVE,
-    DEAD,
-}
+parcelable LauncherUserInfo;
diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java
new file mode 100644
index 0000000..214c3e4
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * The LauncherUserInfo object holds information about an Android user that is required to display
+ * the Launcher related UI elements specific to the user (like badges).
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+public final class LauncherUserInfo implements Parcelable {
+
+    private final String mUserType;
+
+    // Serial number for the user, should be same as in the {@link UserInfo} object.
+    private final int mUserSerialNumber;
+
+    /**
+     * Returns type of the user as defined in {@link UserManager}. e.g.,
+     * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE}
+     * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug
+     * is resolved.
+     *
+     * @return the userType for the user whose LauncherUserInfo this is
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    @NonNull
+    public String getUserType() {
+        return mUserType;
+    }
+
+    /**
+     * Returns serial number of user as returned by
+     * {@link UserManager#getSerialNumberForUser(UserHandle)}
+     *
+     * @return the serial number associated with the user
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public int getUserSerialNumber() {
+        return mUserSerialNumber;
+    }
+
+    private LauncherUserInfo(@NonNull Parcel in) {
+        mUserType = in.readString16NoHelper();
+        mUserSerialNumber = in.readInt();
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString16NoHelper(mUserType);
+        dest.writeInt(mUserSerialNumber);
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public static final @android.annotation.NonNull Creator<LauncherUserInfo> CREATOR =
+            new Creator<LauncherUserInfo>() {
+                @Override
+                public LauncherUserInfo createFromParcel(Parcel in) {
+                    return new LauncherUserInfo(in);
+                }
+
+                @Override
+                public LauncherUserInfo[] newArray(int size) {
+                    return new LauncherUserInfo[size];
+                }
+            };
+
+    /**
+     * @hide
+     */
+    public static final class Builder {
+        private final String mUserType;
+
+        private final int mUserSerialNumber;
+
+        public Builder(@NonNull String userType, int userSerialNumber) {
+            this.mUserType = userType;
+            this.mUserSerialNumber = userSerialNumber;
+        }
+
+        /**
+         * Builds the LauncherUserInfo object
+         */
+        @NonNull public LauncherUserInfo build() {
+            return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber);
+        }
+
+    } // End builder
+
+    private LauncherUserInfo(@NonNull  String userType, int userSerialNumber) {
+        this.mUserType = userType;
+        this.mUserSerialNumber = userSerialNumber;
+    }
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 673a8a5..cd8938d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -371,6 +371,13 @@
             "android.content.pm.extra.UNARCHIVE_ALL_USERS";
 
     /**
+     * A list of warnings that occurred during installation.
+     *
+     * @hide
+     */
+    public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS";
+
+    /**
      * Streaming installation pending.
      * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
      *
@@ -993,6 +1000,37 @@
         }
     }
 
+    /**
+     * Install package in an archived state.
+     *
+     * @param archivedPackage archived package data such as package name, signature etc.
+     * @param sessionParams used to create an underlying installation session
+     * @param statusReceiver Called when the state of the session changes. Intents
+     *                       sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
+     *                       individual status codes on how to handle them.
+     * @see #createSession
+     * @see PackageInstaller.Session#commit
+     */
+    @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public void installPackageArchived(@NonNull ArchivedPackage archivedPackage,
+            @NonNull SessionParams sessionParams,
+            @NonNull IntentSender statusReceiver) {
+        Objects.requireNonNull(archivedPackage, "archivedPackage cannot be null");
+        Objects.requireNonNull(sessionParams, "sessionParams cannot be null");
+        Objects.requireNonNull(statusReceiver, "statusReceiver cannot be null");
+        try {
+            mInstaller.installPackageArchived(
+                    archivedPackage.getParcel(),
+                    sessionParams,
+                    statusReceiver,
+                    mInstallerPackageName,
+                    new UserHandle(mUserId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** {@hide} */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
@@ -2723,8 +2761,8 @@
          * Sets the state of permissions for the package at installation.
          * <p/>
          * Granting any runtime permissions require the
-         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS
-         * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime
+         * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS}
+         * permission to be held by the caller. Revoking runtime
          * permissions is not allowed, even during app update sessions.
          * <p/>
          * Holders without the permission are allowed to change the following special permissions:
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8fbe50c3..ad7dd51 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -838,7 +838,7 @@
             GET_DISABLED_COMPONENTS,
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
-            FILTER_OUT_QUARANTINED_COMPONENTS,
+            MATCH_QUARANTINED_COMPONENTS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ComponentInfoFlagsBits {}
@@ -863,7 +863,7 @@
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
             MATCH_CLONE_PROFILE,
-            FILTER_OUT_QUARANTINED_COMPONENTS,
+            MATCH_QUARANTINED_COMPONENTS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResolveInfoFlagsBits {}
@@ -1252,12 +1252,15 @@
      */
     // TODO(b/278553670) Unhide and update @links before launch.
     @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
 
     /**
-     * @hide
+     * Querying flag: always match components of packages in quarantined state.
+     * @see #isPackageQuarantined
      */
-    public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L;
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
+    public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
 
     /**
      * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
@@ -3775,6 +3778,7 @@
      * The device is capable of communicating with other devices via
      * <a href="https://www.threadgroup.org">Thread</a> networking protocol.
      */
+    @FlaggedApi("com.android.net.thread.flags.thread_enabled")
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
 
@@ -4605,6 +4609,16 @@
     public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
             "android.software.wallet_location_based_suggestions";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the rotary encoder hardware to support rotating bezel on watch.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
+            "android.hardware.rotaryencoder.lowres";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
@@ -9878,17 +9892,32 @@
     }
 
     /**
+     * Query if an app is currently stopped.
+     *
+     * @return {@code true} if the given package is stopped, {@code false} otherwise
+     * @throws NameNotFoundException if the package could not be found.
+     * @see ApplicationInfo#FLAG_STOPPED
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED)
+    public boolean isPackageStopped(@NonNull String packageName) throws NameNotFoundException {
+        throw new UnsupportedOperationException("isPackageStopped not implemented");
+    }
+
+    /**
      * Query if an app is currently quarantined.
+     * A misbehaving app can be quarantined by e.g. a system of another privileged entity.
+     * Quarantined apps are similar to disabled, but still visible in e.g. Launcher.
+     * Only activities of such apps can still be queried, but not services etc.
+     * Quarantined apps can't be bound to, and won't receive broadcasts.
+     * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified.
      *
      * @return {@code true} if the given package is quarantined, {@code false} otherwise
      * @throws NameNotFoundException if the package could not be found.
-     *
-     * @hide
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
         throw new UnsupportedOperationException("isPackageQuarantined not implemented");
     }
-
     /**
      * Provide a hint of what the {@link ApplicationInfo#category} value should
      * be for the given package.
@@ -10998,6 +11027,16 @@
                 "makeUidVisible not implemented in subclass");
     }
 
+    /**
+     * Return archived package info for the package or null if the package is not installed.
+     * @see PackageInstaller#installPackageArchived
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+    public @Nullable ArchivedPackage getArchivedPackage(@NonNull String packageName) {
+        throw new UnsupportedOperationException(
+                "getArchivedPackage not implemented in subclass");
+    }
+
     // Some of the flags don't affect the query result, but let's be conservative and cache
     // each combination of flags separately.
 
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 65f56f6..9869179 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -132,11 +132,6 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
      * transfer over network between device and cloud.
-     *
-     * <p class="note">
-     * Use the {@link android.app.job.JobInfo.Builder#setDataTransfer} API for data transfers
-     * that can be deferred until conditions are ideal for the app or device.
-     * </p>
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl
deleted file mode 100644
index 36c127a..0000000
--- a/core/java/android/content/pm/Signature.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/* //device/java/android/android/view/WindowManager.aidl
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES 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;
-
-/* For the key attestation application id provider service we needed a native implementation
- * of the Signature parcelable because the service is used by the native keystore.
- * The native implementation is now located at
- * system/security/keystore/Signature.cpp
- * and
- * system/security/keystore/include/keystore/Signature.h.
- * and can be used by linking against libkeystore_binder.
- *
- * This is not the best arrangement. If you, dear reader, happen to implement native implementations
- * for the package manager's parcelables, consider moving Signature.cpp/.h to your library and
- * adjust keystore's dependencies accordingly. Thank you.
- */
-parcelable Signature cpp_header "keystore/Signature.h";
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index ee9aaca3..543703e 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -16,9 +16,16 @@
 
 package android.content.pm;
 
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.security.PublicKey;
+import java.util.Collection;
 
 /**
  * Information pertaining to the signing certificates used to sign a package.
@@ -33,6 +40,43 @@
     }
 
     /**
+     * Creates a new instance of information used to sign the APK.
+     *
+     * @param schemeVersion version of signing schema.
+     * @param apkContentsSigners signing certificates.
+     * @param publicKeys for the signing certificates.
+     * @param signingCertificateHistory All signing certificates the package has proven it is
+     *                                  authorized to use.
+     *
+     * @see
+     * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing
+     * schemas</a>
+     */
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public SigningInfo(@IntRange(from = 0) int schemeVersion,
+            @Nullable Collection<Signature> apkContentsSigners,
+            @Nullable Collection<PublicKey> publicKeys,
+            @Nullable Collection<Signature> signingCertificateHistory) {
+        if (schemeVersion <= 0 || apkContentsSigners == null) {
+            mSigningDetails = SigningDetails.UNKNOWN;
+            return;
+        }
+        Signature[] signatures = apkContentsSigners != null && !apkContentsSigners.isEmpty()
+                ? apkContentsSigners.toArray(new Signature[apkContentsSigners.size()])
+                : null;
+        Signature[] pastSignatures =
+                signingCertificateHistory != null && !signingCertificateHistory.isEmpty()
+                ? signingCertificateHistory.toArray(new Signature[signingCertificateHistory.size()])
+                : null;
+        if (Signature.areExactArraysMatch(signatures, pastSignatures)) {
+            pastSignatures = null;
+        }
+        ArraySet<PublicKey> keys =
+                publicKeys != null && !publicKeys.isEmpty() ? new ArraySet<>(publicKeys) : null;
+        mSigningDetails = new SigningDetails(signatures, schemeVersion, keys, pastSignatures);
+    }
+
+    /**
      * @hide only packagemanager should be populating this
      */
     public SigningInfo(SigningDetails signingDetails) {
@@ -116,6 +160,26 @@
         return mSigningDetails.getSignatures();
     }
 
+    /**
+     * Returns the version of signing schema used to sign the APK.
+     *
+     * @see
+     * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing
+     * schemas</a>
+     */
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public @IntRange(from = 0) int getSchemeVersion() {
+        return mSigningDetails.getSignatureSchemeVersion();
+    }
+
+    /**
+     * Returns the public keys for the signing certificates.
+     */
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public @NonNull Collection<PublicKey> getPublicKeys() {
+        return mSigningDetails.getPublicKeys();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -126,6 +190,12 @@
         mSigningDetails.writeToParcel(dest, parcelableFlags);
     }
 
+    /* @hide */
+    @NonNull
+    SigningDetails getSigningDetails() {
+        return mSigningDetails;
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
             new Parcelable.Creator<SigningInfo>() {
         @Override
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 96af2b6..884d463 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -49,6 +49,7 @@
     private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
     private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
+    private static final String ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE = "hideInSettingsInQuietMode";
     private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
     private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
     private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA =
@@ -78,6 +79,7 @@
             INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT,
             INDEX_DELETE_APP_WITH_PARENT,
             INDEX_ALWAYS_VISIBLE,
+            INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -94,6 +96,7 @@
     private static final int INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT = 9;
     private static final int INDEX_DELETE_APP_WITH_PARENT = 10;
     private static final int INDEX_ALWAYS_VISIBLE = 11;
+    private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -324,6 +327,7 @@
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
             setShowInSettings(orig.getShowInSettings());
+            setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode());
             setUseParentsContacts(orig.getUseParentsContacts());
         }
         if (hasQueryOrManagePermission) {
@@ -409,6 +413,42 @@
     private @ShowInSettings int mShowInSettings;
 
     /**
+     * Returns whether a user should be shown in the Settings app depending on the quiet mode.
+     * This is generally inapplicable for non-profile users.
+     *
+     * <p> {@link #getShowInSettings()} returns whether / how a user should be shown in Settings.
+     * However, if this behaviour should be changed based on the quiet mode of the user, then this
+     * property can be used. If the property is not set then the user is shown in the Settings app
+     * irrespective of whether the user is in quiet mode or not. If the property is set, then the
+     * user is shown in the Settings app only if the user is not in the quiet mode. Please note that
+     * this property takes effect only if {@link #getShowInSettings()} does not return
+     * {@link #SHOW_IN_SETTINGS_NO}.
+     *
+     * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+     * property.
+     *
+     * @return true if a profile should be shown in the Settings only when the user is not in the
+     * quiet mode.
+     *
+     * See also {@link #getShowInSettings()}, {@link #setShowInSettings(int)},
+     * {@link ShowInSettings}
+     *
+     * @hide
+     */
+    public boolean getHideInSettingsInQuietMode() {
+        if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) return mHideInSettingsInQuietMode;
+        if (mDefaultProperties != null) return mDefaultProperties.mHideInSettingsInQuietMode;
+        throw new SecurityException(
+                "You don't have permission to query HideInSettingsInQuietMode");
+    }
+    /** @hide */
+    public void setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) {
+        this.mHideInSettingsInQuietMode = hideInSettingsInQuietMode;
+        setPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE);
+    }
+    private boolean mHideInSettingsInQuietMode;
+
+    /**
      * Returns whether a profile should be started when its parent starts (unless in quiet mode).
      * This only applies for users that have parents (i.e. for profiles).
      * @hide
@@ -724,6 +764,9 @@
                 case ATTR_SHOW_IN_SETTINGS:
                     setShowInSettings(parser.getAttributeInt(i));
                     break;
+                case ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE:
+                    setHideInSettingsInQuietMode(parser.getAttributeBoolean(i));
+                    break;
                 case ATTR_INHERIT_DEVICE_POLICY:
                     setInheritDevicePolicy(parser.getAttributeInt(i));
                     break;
@@ -777,6 +820,10 @@
         if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
             serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
         }
+        if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) {
+            serializer.attributeBoolean(null, ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE,
+                    mHideInSettingsInQuietMode);
+        }
         if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) {
             serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
                     mInheritDevicePolicy);
@@ -823,6 +870,7 @@
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
         dest.writeInt(mShowInSettings);
+        dest.writeBoolean(mHideInSettingsInQuietMode);
         dest.writeInt(mInheritDevicePolicy);
         dest.writeBoolean(mUseParentsContacts);
         dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
@@ -845,6 +893,7 @@
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
         mShowInSettings = source.readInt();
+        mHideInSettingsInQuietMode = source.readBoolean();
         mInheritDevicePolicy = source.readInt();
         mUseParentsContacts = source.readBoolean();
         mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
@@ -881,6 +930,7 @@
         private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
         private boolean mStartWithParent = false;
         private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
+        private boolean mHideInSettingsInQuietMode = false;
         private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
         private boolean mUseParentsContacts = false;
         private boolean mUpdateCrossProfileIntentFiltersOnOTA = false;
@@ -910,6 +960,12 @@
             return this;
         }
 
+        /** Sets the value for {@link #mHideInSettingsInQuietMode} */
+        public Builder setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) {
+            mHideInSettingsInQuietMode = hideInSettingsInQuietMode;
+            return this;
+        }
+
         /** Sets the value for {@link #mInheritDevicePolicy}*/
         public Builder setInheritDevicePolicy(
                 @InheritDevicePolicy int inheritRestrictionsDevicePolicy) {
@@ -972,6 +1028,7 @@
                     mShowInLauncher,
                     mStartWithParent,
                     mShowInSettings,
+                    mHideInSettingsInQuietMode,
                     mInheritDevicePolicy,
                     mUseParentsContacts,
                     mUpdateCrossProfileIntentFiltersOnOTA,
@@ -989,6 +1046,7 @@
             @ShowInLauncher int showInLauncher,
             boolean startWithParent,
             @ShowInSettings int showInSettings,
+            boolean hideInSettingsInQuietMode,
             @InheritDevicePolicy int inheritDevicePolicy,
             boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA,
             @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl,
@@ -1001,6 +1059,7 @@
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
         setShowInSettings(showInSettings);
+        setHideInSettingsInQuietMode(hideInSettingsInQuietMode);
         setInheritDevicePolicy(inheritDevicePolicy);
         setUseParentsContacts(useParentsContacts);
         setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 89ca915..96609ad 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -15,10 +15,11 @@
 }
 
 flag {
-    name: "prevent_sdk_lib_app"
+    name: "disallow_sdk_libs_to_be_apps"
     namespace: "package_manager_service"
-    description: "Feature flag to enable the prevent sdk-library be an application."
+    description: "Feature flag to disallow a <sdk-library> to be an <application>."
     bug: "295843617"
+    is_fixed_read_only: true
 }
 
 flag {
@@ -42,3 +43,18 @@
     description: "Feature flag to enable the feature to retrieve package info without installation."
     bug: "269149275"
 }
+
+flag {
+    name: "use_art_service_v2"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
+    bug: "304741685"
+}
+
+flag {
+    name: "sdk_lib_independence"
+    namespace: "package_manager_service"
+    description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable."
+    bug: "295827951"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
index 1e60abb..7ada946 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
@@ -231,7 +231,7 @@
     }
 
     /**
-     * Mapping of domain host to state, as defined by {@link DomainState}.
+     * Mapping of domain host to state, as defined by the {@code DOMAIN_STATE_*} constants
      */
     @DataClass.Generated.Member
     public @NonNull Map<String,Integer> getHostToStateMap() {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ed22284..1b37092 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2210,8 +2210,7 @@
      *
      * <p>The best practices is to obtain metrics from
      * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from
-     * this API may be wrong if {@link Context#getResources()} is from
-     * non-{@link android.annotation.UiContext}.
+     * this API may be wrong if {@link Context#getResources()} is not from a {@code UiContext}.
      * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()}
      * to build {@link android.app.Activity} UI elements especially when the
      * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}.
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 5cc3b92..c7790bd 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@
 import android.annotation.RawRes;
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
+import android.app.LocaleConfig;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -426,38 +428,59 @@
 
                 String[] selectedLocales = null;
                 String defaultLocale = null;
+                LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig();
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
-                        String[] availableLocales;
-                        // The LocaleList has changed. We must query the AssetManager's
-                        // available Locales and figure out the best matching Locale in the new
-                        // LocaleList.
-                        availableLocales = mAssets.getNonSystemLocales();
-                        if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                            // No app defined locales, so grab the system locales.
-                            availableLocales = mAssets.getLocales();
-                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                                availableLocales = null;
+                        if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+                            Locale[] intersection =
+                                    locales.getIntersection(lc.getSupportedLocales());
+                            mConfiguration.setLocales(new LocaleList(intersection));
+                            selectedLocales = new String[intersection.length];
+                            for (int i = 0; i < intersection.length; i++) {
+                                selectedLocales[i] =
+                                        adjustLanguageTag(intersection[i].toLanguageTag());
                             }
-                        }
+                            defaultLocale =
+                                    adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+                        } else {
+                            String[] availableLocales;
+                            // The LocaleList has changed. We must query the AssetManager's
+                            // available Locales and figure out the best matching Locale in the new
+                            // LocaleList.
+                            availableLocales = mAssets.getNonSystemLocales();
+                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                // No app defined locales, so grab the system locales.
+                                availableLocales = mAssets.getLocales();
+                                if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                    availableLocales = null;
+                                }
+                            }
 
-                        if (availableLocales != null) {
-                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                    availableLocales);
-                            if (bestLocale != null) {
-                                selectedLocales = new String[]{
-                                        adjustLanguageTag(bestLocale.toLanguageTag())};
-                                if (!bestLocale.equals(locales.get(0))) {
-                                    mConfiguration.setLocales(
-                                            new LocaleList(bestLocale, locales));
+                            if (availableLocales != null) {
+                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                        availableLocales);
+                                if (bestLocale != null) {
+                                    selectedLocales = new String[]{
+                                            adjustLanguageTag(bestLocale.toLanguageTag())};
+                                    if (!bestLocale.equals(locales.get(0))) {
+                                        mConfiguration.setLocales(
+                                                new LocaleList(bestLocale, locales));
+                                    }
                                 }
                             }
                         }
                     }
                 }
                 if (selectedLocales == null) {
-                    selectedLocales = new String[]{
-                            adjustLanguageTag(locales.get(0).toLanguageTag())};
+                    if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+                        selectedLocales = new String[locales.size()];
+                        for (int i = 0; i < locales.size(); i++) {
+                            selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+                        }
+                    } else {
+                        selectedLocales = new String[]{
+                                adjustLanguageTag(locales.get(0).toLanguageTag())};
+                    }
                 }
 
                 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
new file mode 100644
index 0000000..1b8eb07
--- /dev/null
+++ b/core/java/android/content/res/flags.aconfig
@@ -0,0 +1,17 @@
+package: "android.content.res"
+
+flag {
+    name: "default_locale"
+    namespace: "resource_manager"
+    description: "Feature flag for default locale in LocaleConfig"
+    bug: "117306409"
+    # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "asset_file_descriptor_frro"
+    namespace: "resource_manager"
+    description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
+    bug: "304478666"
+}
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index c344004..8f07d19 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
 import android.os.CancellationSignal;
 import android.os.OutcomeReceiver;
 
@@ -28,8 +28,8 @@
 
 /**
  * Represents an error encountered during the
- * {@link CredentialManager#createCredential(CreateCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
  */
 public class CreateCredentialException extends Exception {
     /**
@@ -41,7 +41,7 @@
 
     /**
      * The error type value for when no create options are available from any provider(s),
-     * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity,
+     * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
      * CancellationSignal, Executor, OutcomeReceiver)} request.
      */
     @NonNull
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index db71624..755e659 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -155,8 +155,7 @@
     }
 
     /**
-     * {@link CredentialDescription#mType} and
-     * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor
+     * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor
      * enforces {@link CredentialEntry} to have the same type and
      * {@link android.app.slice.Slice} contained by the entry can not be hashed.
      */
@@ -166,8 +165,7 @@
     }
 
     /**
-     * {@link CredentialDescription#mType} and
-     * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check.
+     * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check.
      */
     @Override
     public boolean equals(Object obj) {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 20771af..524afe9 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -153,7 +153,7 @@
                     mService.getCandidateCredentials(
                             request,
                             new GetCandidateCredentialsTransport(executor, callback),
-                            mContext.getOpPackageName());
+                            callingPackage);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 95ca011..38fbd72 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -16,12 +16,14 @@
 
 package android.credentials;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
+import android.credentials.flags.Flags;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -42,6 +44,7 @@
     @NonNull private final List<String> mCapabilities = new ArrayList<>();
     @Nullable private final CharSequence mOverrideLabel;
     @Nullable private CharSequence mSettingsSubtitle = null;
+    @Nullable private CharSequence mSettingsActivity = null;
     private final boolean mIsSystemProvider;
     private final boolean mIsEnabled;
     private final boolean mIsPrimary;
@@ -59,6 +62,7 @@
         mIsEnabled = builder.mIsEnabled;
         mIsPrimary = builder.mIsPrimary;
         mOverrideLabel = builder.mOverrideLabel;
+        mSettingsActivity = builder.mSettingsActivity;
     }
 
     /** Returns true if the service supports the given {@code credentialType}, false otherwise. */
@@ -104,10 +108,7 @@
         return mIsEnabled;
     }
 
-    /**
-     * Returns whether the provider is set as primary by the user.
-     *
-     */
+    /** Returns whether the provider is set as primary by the user. */
     public boolean isPrimary() {
         return mIsPrimary;
     }
@@ -118,6 +119,23 @@
         return mSettingsSubtitle;
     }
 
+    /**
+     * Returns the settings activity.
+     *
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
+    public CharSequence getSettingsActivity() {
+        // Add a manual check to make sure this returns null if
+        // the flag is not enabled.
+        if (!Flags.settingsActivityEnabled()) {
+            return null;
+        }
+        return mSettingsActivity;
+    }
+
     /** Returns the component name for the service. */
     @NonNull
     public ComponentName getComponentName() {
@@ -133,6 +151,7 @@
         dest.writeBoolean(mIsPrimary);
         TextUtils.writeToParcel(mOverrideLabel, dest, flags);
         TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
+        TextUtils.writeToParcel(mSettingsActivity, dest, flags);
     }
 
     @Override
@@ -161,6 +180,9 @@
                 + "settingsSubtitle="
                 + mSettingsSubtitle
                 + ", "
+                + "settingsActivity="
+                + mSettingsActivity
+                + ", "
                 + "capabilities="
                 + String.join(",", mCapabilities)
                 + "}";
@@ -174,6 +196,7 @@
         mIsPrimary = in.readBoolean();
         mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
     }
 
     public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
@@ -196,6 +219,7 @@
         @NonNull private List<String> mCapabilities = new ArrayList<>();
         private boolean mIsSystemProvider = false;
         @Nullable private CharSequence mSettingsSubtitle = null;
+        @Nullable private CharSequence mSettingsActivity = null;
         private boolean mIsEnabled = false;
         private boolean mIsPrimary = false;
         @Nullable private CharSequence mOverrideLabel = null;
@@ -231,6 +255,16 @@
             return this;
         }
 
+        /**
+         * Sets the settings activity.
+         *
+         * @hide
+         */
+        public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) {
+            mSettingsActivity = settingsActivity;
+            return this;
+        }
+
         /** Sets a list of capabilities this provider service can support. */
         public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
             mCapabilities.addAll(capabilities);
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 720c53b..0421d1f 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
 import android.os.CancellationSignal;
 import android.os.OutcomeReceiver;
 
@@ -28,8 +28,8 @@
 
 /**
  * Represents an error encountered during the
- * {@link CredentialManager#getCredential(GetCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
  */
 public class GetCredentialException extends Exception {
     /**
@@ -41,7 +41,7 @@
 
     /**
      * The error type value for when no credential is found available for the given {@link
-     * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
+     * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal,
      * Executor, OutcomeReceiver)} request.
      */
     @NonNull
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 056b18a..212f571 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,7 +22,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.IntentSender;
@@ -36,7 +35,7 @@
 /**
  * A response object that prefetches user app credentials and provides metadata about them. It can
  * then be used to issue the full credential retrieval flow via the
- * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
  * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection
  * and credential selection, to officially retrieve a credential.
  */
@@ -44,7 +43,7 @@
 
     /**
      * A handle that represents a pending get-credential operation. Pass this handle to {@link
-     * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+     * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
      * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a
      * credential.
      */
@@ -144,7 +143,7 @@
 
     /**
      * Returns a handle that represents this pending get-credential operation. Pass this handle to
-     * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity,
+     * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle,
      * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially
      * retrieve a credential.
      */
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 0d0305b..9b819a7 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -5,4 +5,11 @@
     name: "settings_activity_enabled"
     description: "Enable the Credential Manager Settings Activity APIs"
     bug: "300014059"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "instant_apps_enabled"
+    description: "Enables Credential Manager to work with Instant Apps"
+    bug: "302190269"
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 746f2f2..8a4f678 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -700,6 +701,7 @@
      *   }
      * </pre>
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionReadOnly() {
         beginTransactionWithListenerReadOnly(null);
     }
@@ -783,6 +785,7 @@
      *   }
      * </pre>
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionWithListenerReadOnly(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2221,6 +2224,7 @@
      * @throws IllegalStateException if a transaction is not in progress.
      * @throws SQLiteException if the SQL cannot be compiled.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     @NonNull
     public SQLiteRawStatement createRawStatement(@NonNull String sql) {
         Objects.requireNonNull(sql);
@@ -2240,6 +2244,7 @@
      * @return The ROWID of the last row to be inserted under this connection.
      * @throws IllegalStateException if there is no current transaction.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getLastInsertRowId() {
         return getThreadSession().getLastInsertRowId();
     }
@@ -2253,6 +2258,7 @@
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getLastChangedRowCount() {
         return getThreadSession().getLastChangedRowCount();
     }
@@ -2280,6 +2286,7 @@
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getTotalChangedRowCount() {
         return getThreadSession().getTotalChangedRowCount();
     }
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 165f181..33f602b 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,6 +71,7 @@
  *
  * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
  */
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
 public final class SQLiteRawStatement implements Closeable {
 
     private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
new file mode 100644
index 0000000..62a5123
--- /dev/null
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.database.sqlite"
+
+flag {
+     name: "sqlite_apis_35"
+     namespace: "system_performance"
+     is_fixed_read_only: true
+     description: "SQLite APIs held back for Android 15"
+     bug: "279043253"
+}
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 1333f0d..a9742cb 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
 
 package android.hardware;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer";
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 8bfc2f7..014cf6d 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -16,21 +16,28 @@
 
 package android.hardware;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.hardware.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import libcore.util.NativeAllocationRegistry;
 
 /**
- * The class provides overlay properties of the device. OverlayProperties
- * exposes some capabilities from HWC e.g. if fp16 can be supported for HWUI.
+ * Provides supported overlay properties of the device.
  *
- * In the future, more capabilities can be added, e.g., whether or not
- * per-layer colorspaces are supported.
- *
- * @hide
+ * <p>
+ * Hardware overlay is a technique to composite different buffers directly
+ * to the screen using display hardware rather than the GPU.
+ * The system compositor is able to assign any content managed by a
+ * {@link android.view.SurfaceControl} onto a hardware overlay if possible.
+ * Applications may be interested in the display hardware capabilities exposed
+ * by this class as a hint to determine if their {@link android.view.SurfaceControl}
+ * tree is power-efficient and performant.
+ * </p>
  */
+@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
 public final class OverlayProperties implements Parcelable {
 
     private static final NativeAllocationRegistry sRegistry =
@@ -38,10 +45,12 @@
             nGetDestructor());
 
     private long mNativeObject;
+    // only for virtual displays
+    private static OverlayProperties sDefaultOverlayProperties;
     // Invoked on destruction
     private Runnable mCloser;
 
-    public OverlayProperties(long nativeObject) {
+    private OverlayProperties(long nativeObject) {
         if (nativeObject != 0) {
             mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
         }
@@ -49,7 +58,20 @@
     }
 
     /**
+     * For virtual displays, we provide an overlay properties object
+     * with RGBA 8888 only, sRGB only, true for mixed color spaces.
+     * @hide
+     */
+    public static OverlayProperties getDefault() {
+        if (sDefaultOverlayProperties == null) {
+            sDefaultOverlayProperties = new OverlayProperties(nCreateDefault());
+        }
+        return sDefaultOverlayProperties;
+    }
+
+    /**
      * @return True if the device can support fp16, false otherwise.
+     * @hide
      */
     public boolean supportFp16ForHdr() {
         if (mNativeObject == 0) {
@@ -59,8 +81,13 @@
     }
 
     /**
-     * @return True if the device can support mixed colorspaces, false otherwise.
+     * Indicates that hw composition of two or more overlays
+     * with different colorspaces is supported on the device.
+     *
+     * @return True if the device can support mixed colorspaces efficiently,
+     *         false if GPU composition fallback is otherwise required.
      */
+    @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
     public boolean supportMixedColorSpaces() {
         if (mNativeObject == 0) {
             return false;
@@ -68,28 +95,14 @@
         return nSupportMixedColorSpaces(mNativeObject);
     }
 
-    /**
-     * Release the local reference.
-     */
-    public void release() {
-        if (mNativeObject != 0) {
-            mCloser.run();
-            mNativeObject = 0;
-        }
-    }
 
+    @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
     @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}.
-     */
+    @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         if (mNativeObject == 0) {
@@ -100,6 +113,7 @@
         nWriteOverlayPropertiesToParcel(mNativeObject, dest);
     }
 
+    @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
     public static final @NonNull Parcelable.Creator<OverlayProperties> CREATOR =
             new Parcelable.Creator<OverlayProperties>() {
         public OverlayProperties createFromParcel(Parcel in) {
@@ -115,6 +129,7 @@
     };
 
     private static native long nGetDestructor();
+    private static native long nCreateDefault();
     private static native boolean nSupportFp16ForHdr(long nativeObject);
     private static native boolean nSupportMixedColorSpaces(long nativeObject);
     private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index f033f97..bcf447b 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -27,6 +28,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -1809,6 +1812,41 @@
     protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
             Sensor sensor, boolean disable);
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION})
+    public @interface DataInjectionMode {}
+    /**
+     * This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
+     * the HAL ignores the sensor data provided by physical sensors and accepts the data that is
+     * injected from the SensorService as if it were the real sensor data. This mode is primarily
+     * used for testing various algorithms like vendor provided SensorFusion, Step Counter and
+     * Step Detector etc. Typically, in this mode, there is a client app which injects
+     * sensor data into the HAL. Normal apps can register and unregister for any sensor
+     * that supports injection. Registering to sensors that do not support injection will
+     * give an error.
+     * This is the default data injection mode.
+     * @hide
+     */
+    public static final int DATA_INJECTION = 1;
+    /**
+     * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+     * delivered to all requesting apps rather than just the package allowed to inject data.
+     * This mode is only allowed to be used on development builds.
+     * @hide
+     */
+    public static final int REPLAY_DATA_INJECTION = 3;
+    /**
+     * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+     * buffer in the platform and played back to all requesting apps.
+     * This is useful for playing back sensor data to test platform components without
+     * relying on the HAL to support data injection.
+     * @hide
+     */
+    public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
+
 
     /**
      * For testing purposes only. Not for third party applications.
@@ -1833,13 +1871,47 @@
      */
     @SystemApi
     public boolean initDataInjection(boolean enable) {
-        return initDataInjectionImpl(enable);
+        return initDataInjectionImpl(enable, DATA_INJECTION);
+    }
+
+    /**
+     * For testing purposes only. Not for third party applications.
+     *
+     * Initialize data injection mode and create a client for data injection. SensorService should
+     * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or
+     * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in
+     * a Data Injection mode, use one of:
+     *
+     * <ul>
+     *      <li>adb shell dumpsys sensorservice data_injection</li>
+     *      <li>adb shell dumpsys sensorservice replay_data_injection package_name</li>
+     *      <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li>
+     * </ul>
+     *
+     * Typically this is done using a host side test.  This mode is expected to be used
+     * only for testing purposes. See {@link DataInjectionMode} for details of each data injection
+     * mode. Once this method succeeds, the test can call
+     * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL.
+     * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable"
+     *
+     * @param enable True to initialize a client in a data injection mode.
+     *               False to clean up the native resources.
+     *
+     * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION.
+     *             See {@link DataInjectionMode} for details.
+     *
+     * @return true if the HAL supports data injection and false
+     *         otherwise.
+     * @hide
+     */
+    public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) {
+        return initDataInjectionImpl(enable, mode);
     }
 
     /**
      * @hide
      */
-    protected abstract boolean initDataInjectionImpl(boolean enable);
+    protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode);
 
     /**
      * For testing purposes only. Not for third party applications.
@@ -1871,9 +1943,6 @@
         if (sensor == null) {
             throw new IllegalArgumentException("sensor cannot be null");
         }
-        if (!sensor.isDataInjectionSupported()) {
-            throw new IllegalArgumentException("sensor does not support data injection");
-        }
         if (values == null) {
             throw new IllegalArgumentException("sensor data cannot be null");
         }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index dfd3802..40e03db 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -90,6 +90,8 @@
     private static native void nativeGetRuntimeSensors(
             long nativeInstance, int deviceId, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+    private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance);
+    private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
             long nativeInstance, int deviceId, long size, int channelType, int fd,
@@ -384,20 +386,41 @@
         }
     }
 
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         synchronized (sLock) {
+            boolean isDataInjectionModeEnabled = false;
             if (enable) {
-                boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+                switch (mode) {
+                    case DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+                        break;
+                    case REPLAY_DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled(
+                                mNativeInstance);
+                        break;
+                    case HAL_BYPASS_REPLAY_DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled(
+                                mNativeInstance);
+                        break;
+                    default:
+                        break;
+                }
                 // The HAL does not support injection OR SensorService hasn't been set in DI mode.
                 if (!isDataInjectionModeEnabled) {
-                    Log.e(TAG, "Data Injection mode not enabled");
+                    Log.e(TAG, "The correct Data Injection mode has not been enabled");
                     return false;
                 }
+                if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) {
+                    // The inject event queue has been initialized for a different type of DI
+                    // close it and create a new one
+                    sInjectEventQueue.dispose();
+                    sInjectEventQueue = null;
+                }
                 // Initialize a client for data_injection.
                 if (sInjectEventQueue == null) {
                     try {
                         sInjectEventQueue = new InjectEventQueue(
-                                mMainLooper, this, mContext.getPackageName());
+                                mMainLooper, this, mode, mContext.getPackageName());
                     } catch (RuntimeException e) {
                         Log.e(TAG, "Cannot create InjectEventQueue: " + e);
                     }
@@ -421,6 +444,12 @@
                 Log.e(TAG, "Data injection mode not activated before calling injectSensorData");
                 return false;
             }
+            if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION
+                    && !sensor.isDataInjectionSupported()) {
+                // DI mode and Replay DI mode require support from the sensor HAL
+                // HAL Bypass mode doesn't require this.
+                throw new IllegalArgumentException("sensor does not support data injection");
+            }
             int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,
                                                          timestamp);
             // If there are any errors in data injection clean up the native resources.
@@ -825,6 +854,8 @@
 
         protected static final int OPERATING_MODE_NORMAL = 0;
         protected static final int OPERATING_MODE_DATA_INJECTION = 1;
+        protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3;
+        protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
 
         BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
             if (packageName == null) packageName = "";
@@ -1134,8 +1165,12 @@
     }
 
     final class InjectEventQueue extends BaseEventQueue {
-        public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) {
-            super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
+
+        private int mMode;
+        public InjectEventQueue(Looper looper, SystemSensorManager manager,
+                @DataInjectionMode int mode, String packageName) {
+            super(looper, manager, mode, packageName);
+            mMode = mode;
         }
 
         int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
@@ -1161,6 +1196,10 @@
         protected void removeSensorEvent(Sensor sensor) {
 
         }
+
+        int getDataInjectionMode() {
+            return mMode;
+        }
     }
 
     protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 490ff64..7a43286 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -805,7 +805,7 @@
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
          */
-        public Signature getSignature() {
+        public @Nullable Signature getSignature() {
             return super.getSignature();
         }
 
@@ -813,7 +813,7 @@
          * Get {@link Cipher} object.
          * @return {@link Cipher} object or null if this doesn't contain one.
          */
-        public Cipher getCipher() {
+        public @Nullable Cipher getCipher() {
             return super.getCipher();
         }
 
@@ -821,7 +821,7 @@
          * Get {@link Mac} object.
          * @return {@link Mac} object or null if this doesn't contain one.
          */
-        public Mac getMac() {
+        public @Nullable Mac getMac() {
             return super.getMac();
         }
 
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 151f819..39fbe83 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.security.identity.IdentityCredential;
 import android.security.identity.PresentationSession;
 import android.security.keystore2.AndroidKeyStoreProvider;
@@ -33,20 +34,35 @@
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
  * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
- * {@link IdentityCredential}, and {@link PresentationSession} objects.
+ * {@link KeyAgreement}, {@link IdentityCredential}, and {@link PresentationSession} objects.
  * @hide
  */
 public class CryptoObject {
     private final Object mCrypto;
 
+    /**
+     * Create from a {@link Signature} object.
+     *
+     * @param signature a {@link Signature} object.
+     */
     public CryptoObject(@NonNull Signature signature) {
         mCrypto = signature;
     }
 
+    /**
+     * Create from a {@link Cipher} object.
+     *
+     * @param cipher a {@link Cipher} object.
+     */
     public CryptoObject(@NonNull Cipher cipher) {
         mCrypto = cipher;
     }
 
+    /**
+     * Create from a {@link Mac} object.
+     *
+     * @param mac a {@link Mac} object.
+     */
     public CryptoObject(@NonNull Mac mac) {
         mCrypto = mac;
     }
@@ -62,10 +78,20 @@
         mCrypto = credential;
     }
 
+    /**
+     * Create from a {@link PresentationSession} object.
+     *
+     * @param session a {@link PresentationSession} object.
+     */
     public CryptoObject(@NonNull PresentationSession session) {
         mCrypto = session;
     }
 
+    /**
+     * Create from a {@link KeyAgreement} object.
+     *
+     * @param keyAgreement a {@link KeyAgreement} object.
+     */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
     public CryptoObject(@NonNull KeyAgreement keyAgreement) {
         mCrypto = keyAgreement;
@@ -75,7 +101,7 @@
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
      */
-    public Signature getSignature() {
+    public @Nullable Signature getSignature() {
         return mCrypto instanceof Signature ? (Signature) mCrypto : null;
     }
 
@@ -83,7 +109,7 @@
      * Get {@link Cipher} object.
      * @return {@link Cipher} object or null if this doesn't contain one.
      */
-    public Cipher getCipher() {
+    public @Nullable Cipher getCipher() {
         return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
     }
 
@@ -91,7 +117,7 @@
      * Get {@link Mac} object.
      * @return {@link Mac} object or null if this doesn't contain one.
      */
-    public Mac getMac() {
+    public @Nullable Mac getMac() {
         return mCrypto instanceof Mac ? (Mac) mCrypto : null;
     }
 
@@ -101,7 +127,7 @@
      * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
      */
     @Deprecated
-    public IdentityCredential getIdentityCredential() {
+    public @Nullable IdentityCredential getIdentityCredential() {
         return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
     }
 
@@ -109,16 +135,18 @@
      * Get {@link PresentationSession} object.
      * @return {@link PresentationSession} object or null if this doesn't contain one.
      */
-    public PresentationSession getPresentationSession() {
+    public @Nullable PresentationSession getPresentationSession() {
         return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
     }
 
     /**
-     * Get {@link PresentationSession} object.
-     * @return {@link PresentationSession} object or null if this doesn't contain one.
+     * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby
+     * two or more parties can agree on a shared secret using public key cryptography.
+     *
+     * @return {@link KeyAgreement} object or null if this doesn't contain one.
      */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
-    public KeyAgreement getKeyAgreement() {
+    public @Nullable KeyAgreement getKeyAgreement() {
         return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null;
     }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index bf77681..db7055b 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -357,7 +357,7 @@
             mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
         }
         mRepeatingRequestImageCallback = new CameraOutputImageCallback(
-                mRepeatingRequestImageReader);
+                mRepeatingRequestImageReader, true /*pruneOlderBuffers*/);
         mRepeatingRequestImageReader
                 .setOnImageAvailableListener(mRepeatingRequestImageCallback, mHandler);
     }
@@ -398,7 +398,8 @@
                         CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT);
             }
 
-            mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader);
+            mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader,
+                    false /*pruneOlderBuffers*/);
             mBurstCaptureImageReader.setOnImageAvailableListener(mBurstCaptureImageCallback,
                     mHandler);
             mCameraBurstSurface = mBurstCaptureImageReader.getSurface();
@@ -1106,7 +1107,9 @@
                 }
 
                 for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap.values()) {
-                    captureStage.first.close();
+                    if (captureStage.first != null) {
+                        captureStage.first.close();
+                    }
                 }
                 mCaptureStageMap.clear();
             }
@@ -1207,6 +1210,7 @@
                 if (mImageProcessor != null) {
                     if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
                         Image img = mCapturePendingMap.get(timestamp).first;
+                        mCapturePendingMap.remove(timestamp);
                         mCaptureStageMap.put(stageId, new Pair<>(img, result));
                         checkAndFireBurstProcessing();
                     } else {
@@ -1303,6 +1307,7 @@
                 reader.detachImage(img);
                 if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
                     Integer stageId = mCapturePendingMap.get(timestamp).second;
+                    mCapturePendingMap.remove(timestamp);
                     Pair<Image, TotalCaptureResult> captureStage =
                             mCaptureStageMap.get(stageId);
                     if (captureStage != null) {
@@ -1402,9 +1407,11 @@
         private HashMap<Long, Pair<Image, OnImageAvailableListener>> mImageListenerMap =
                 new HashMap<>();
         private boolean mOutOfBuffers = false;
+        private final boolean mPruneOlderBuffers;
 
-        CameraOutputImageCallback(ImageReader imageReader) {
+        CameraOutputImageCallback(ImageReader imageReader, boolean pruneOlderBuffers) {
             mImageReader = imageReader;
+            mPruneOlderBuffers = pruneOlderBuffers;
         }
 
         @Override
@@ -1447,6 +1454,10 @@
                 ArrayList<Long> removedTs = new ArrayList<>();
                 for (long ts : timestamps) {
                     if (ts < timestamp) {
+                        if (!mPruneOlderBuffers) {
+                            Log.w(TAG, "Unexpected older image with ts: " + ts);
+                            continue;
+                        }
                         Log.e(TAG, "Dropped image with ts: " + ts);
                         Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
                         if (entry.second != null) {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 5e53373..95526a8 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1403,6 +1403,7 @@
                 if (mSurfaces.get(i) != other.mSurfaces.get(i))
                     return false;
             }
+            if (!mIsDeferredConfig && mSurfaces.size() != other.mSurfaces.size()) return false;
             if (mDynamicRangeProfile != other.mDynamicRangeProfile) {
                 return false;
             }
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 99f3d15..53a9a75 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -59,7 +59,8 @@
 
     @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
             BRIGHTNESS_MAX_REASON_NONE,
-            BRIGHTNESS_MAX_REASON_THERMAL
+            BRIGHTNESS_MAX_REASON_THERMAL,
+            BRIGHTNESS_MAX_REASON_POWER_IC
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BrightnessMaxReason {}
@@ -74,6 +75,11 @@
      */
     public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;
 
+    /**
+     * Maximum brightness is restricted due to power throttling.
+     */
+    public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2;
+
     /** Brightness */
     public final float brightness;
 
@@ -144,6 +150,8 @@
                 return "none";
             case BRIGHTNESS_MAX_REASON_THERMAL:
                 return "thermal";
+            case BRIGHTNESS_MAX_REASON_POWER_IC:
+                return "power IC";
         }
         return "invalid";
     }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 990ebc5..8e49c4c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -20,6 +20,7 @@
 import static android.view.Display.HdrCapabilities.HdrType;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -27,10 +28,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 import android.app.KeyguardManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -45,6 +46,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -70,7 +72,11 @@
 @SystemService(Context.DISPLAY_SERVICE)
 public final class DisplayManager {
     private static final String TAG = "DisplayManager";
-    private static final boolean DEBUG = false;
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayManager DEBUG && adb reboot'
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+            || Log.isLoggable("DisplayManager_All", Log.DEBUG);
     private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = true;
 
     /**
@@ -377,7 +383,7 @@
      * @see #createVirtualDisplay
      * @hide
      */
-    @SuppressLint("UnflaggedApi")
+    @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS)
     @SystemApi
     public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
 
@@ -775,7 +781,8 @@
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
             @Nullable Handler handler, @EventsMask long eventsMask) {
-        mGlobal.registerDisplayListener(listener, handler, eventsMask);
+        mGlobal.registerDisplayListener(listener, handler, eventsMask,
+                ActivityThread.currentPackageName());
     }
 
     /**
@@ -1819,6 +1826,12 @@
         String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
 
         /**
+         * Key for the power throttling data as a String formatted, from the display
+         * device config.
+         */
+        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)}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 6d6085b..75f0ceb 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -41,12 +42,14 @@
 import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.Trace;
+import android.sysprop.DisplayProperties;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
@@ -72,7 +75,15 @@
  */
 public final class DisplayManagerGlobal {
     private static final String TAG = "DisplayManager";
-    private static final boolean DEBUG = false;
+
+    private static final String EXTRA_LOGGING_PACKAGE_NAME =
+            DisplayProperties.debug_vri_package().orElse(null);
+    private static String sCurrentPackageName = ActivityThread.currentPackageName();
+    private static boolean sExtraDisplayListenerLogging = initExtraLogging();
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayManager DEBUG && adb reboot'
+    private static final boolean DEBUG = DisplayManager.DEBUG || sExtraDisplayListenerLogging;
 
     // True if display info and display ids should be cached.
     //
@@ -130,6 +141,8 @@
     @VisibleForTesting
     public DisplayManagerGlobal(IDisplayManager dm) {
         mDm = dm;
+        initExtraLogging();
+
         try {
             mWideColorSpace =
                     ColorSpace.get(
@@ -321,12 +334,14 @@
      * If null, listener will use the handler for the current thread, and if still null,
      * the handler for the main thread.
      * If that is still null, a runtime exception will be thrown.
+     * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @Nullable Handler handler, @EventsMask long eventsMask) {
+            @Nullable Handler handler, @EventsMask long eventsMask, String packageName) {
         Looper looper = getLooperForHandler(handler);
         Handler springBoard = new Handler(looper);
-        registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask);
+        registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask,
+                packageName);
     }
 
     /**
@@ -334,9 +349,11 @@
      *
      * @param listener The listener that will be called when display changes occur.
      * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
+     * @param eventsMask Mask of events to be listened to.
+     * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @NonNull Executor executor, @EventsMask long eventsMask) {
+            @NonNull Executor executor, @EventsMask long eventsMask, String packageName) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
         }
@@ -345,15 +362,22 @@
             throw new IllegalArgumentException("The set of events to listen to must not be empty.");
         }
 
+        if (extraLogging()) {
+            Slog.i(TAG, "Registering Display Listener: "
+                    + Long.toBinaryString(eventsMask) + ", packageName: " + packageName);
+        }
+
         synchronized (mLock) {
             int index = findDisplayListenerLocked(listener);
             if (index < 0) {
-                mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask));
+                mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask,
+                        packageName));
                 registerCallbackIfNeededLocked();
             } else {
                 mDisplayListeners.get(index).setEventsMask(eventsMask);
             }
             updateCallbackIfNeededLocked();
+            maybeLogAllDisplayListeners();
         }
     }
 
@@ -362,6 +386,10 @@
             throw new IllegalArgumentException("listener must not be null");
         }
 
+        if (extraLogging()) {
+            Slog.i(TAG, "Unregistering Display Listener: " + listener);
+        }
+
         synchronized (mLock) {
             int index = findDisplayListenerLocked(listener);
             if (index >= 0) {
@@ -371,6 +399,30 @@
                 updateCallbackIfNeededLocked();
             }
         }
+        maybeLogAllDisplayListeners();
+    }
+
+    private void maybeLogAllDisplayListeners() {
+        if (!extraLogging()) {
+            return;
+        }
+
+        Slog.i(TAG, "Currently Registered Display Listeners:");
+        for (int i = 0; i < mDisplayListeners.size(); i++) {
+            Slog.i(TAG, i + ": " + mDisplayListeners.get(i));
+        }
+    }
+
+    /**
+     * Called when there is a display-related window configuration change. Reroutes the event from
+     * WindowManager to make sure the {@link Display} fields are up-to-date in the last callback.
+     * @param displayId the logical display that was changed.
+     */
+    public void handleDisplayChangeFromWindowManager(int displayId) {
+        // There can be racing condition between DMS and WMS callbacks, so force triggering the
+        // listener to make sure the client can get the onDisplayChanged callback even if
+        // DisplayInfo is not changed (Display read from both DisplayInfo and WindowConfiguration).
+        handleDisplayEvent(displayId, EVENT_DISPLAY_CHANGED, true /* forceUpdate */);
     }
 
     private static Looper getLooperForHandler(@Nullable Handler handler) {
@@ -431,7 +483,7 @@
         }
     }
 
-    private void handleDisplayEvent(int displayId, @DisplayEvent int event) {
+    private void handleDisplayEvent(int displayId, @DisplayEvent int event, boolean forceUpdate) {
         final DisplayInfo info;
         synchronized (mLock) {
             if (USE_CACHE) {
@@ -462,7 +514,7 @@
         // Accepting an Executor means the listener may be synchronously invoked, so we must
         // not be holding mLock when we do so
         for (DisplayListenerDelegate listener : mDisplayListeners) {
-            listener.sendDisplayEvent(displayId, event, info);
+            listener.sendDisplayEvent(displayId, event, info, forceUpdate);
         }
     }
 
@@ -1137,7 +1189,7 @@
                 Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString(
                         event));
             }
-            handleDisplayEvent(displayId, event);
+            handleDisplayEvent(displayId, event, false /* forceUpdate */);
         }
     }
 
@@ -1148,79 +1200,96 @@
         private final DisplayInfo mDisplayInfo = new DisplayInfo();
         private final Executor mExecutor;
         private AtomicLong mGenerationId = new AtomicLong(1);
+        private final String mPackageName;
 
         DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
-                @EventsMask long eventsMask) {
+                @EventsMask long eventsMask, String packageName) {
             mExecutor = executor;
             mListener = listener;
             mEventsMask = eventsMask;
+            mPackageName = packageName;
         }
 
-        public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) {
+        void sendDisplayEvent(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info,
+                boolean forceUpdate) {
+            if (extraLogging()) {
+                Slog.i(TAG, "Sending Display Event: " + eventToString(event));
+            }
             long generationId = mGenerationId.get();
-            Message msg = Message.obtain(null, event, displayId, 0, info);
             mExecutor.execute(() -> {
-                // If the generation id's don't match we were canceled but still need to recycle()
+                // If the generation id's don't match we were canceled
                 if (generationId == mGenerationId.get()) {
-                    handleMessage(msg);
+                    handleDisplayEventInner(displayId, event, info, forceUpdate);
                 }
-                msg.recycle();
             });
         }
 
-        public void clearEvents() {
+        void clearEvents() {
             mGenerationId.incrementAndGet();
         }
 
-        public void setEventsMask(@EventsMask long newEventsMask) {
+        void setEventsMask(@EventsMask long newEventsMask) {
             mEventsMask = newEventsMask;
         }
 
-        private void handleMessage(Message msg) {
+        private void handleDisplayEventInner(int displayId, @DisplayEvent int event,
+                @Nullable DisplayInfo info, boolean forceUpdate) {
+            if (extraLogging()) {
+                Slog.i(TAG, "DLD(" + eventToString(event)
+                        + ", display=" + displayId
+                        + ", mEventsMask=" + Long.toBinaryString(mEventsMask)
+                        + ", mPackageName=" + mPackageName
+                        + ", displayInfo=" + info
+                        + ", listener=" + mListener.getClass() + ")");
+            }
             if (DEBUG) {
                 Trace.beginSection(
-                        "DisplayListenerDelegate(" + eventToString(msg.what)
-                                + ", display=" + msg.arg1
-                                + ", listener=" + mListener.getClass() + ")");
+                        TextUtils.trimToSize(
+                                "DLD(" + eventToString(event)
+                                + ", display=" + displayId
+                                + ", listener=" + mListener.getClass() + ")", 127));
             }
-            switch (msg.what) {
+            switch (event) {
                 case EVENT_DISPLAY_ADDED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
-                        mListener.onDisplayAdded(msg.arg1);
+                        mListener.onDisplayAdded(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_CHANGED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
-                        DisplayInfo newInfo = (DisplayInfo) msg.obj;
-                        if (newInfo != null && !newInfo.equals(mDisplayInfo)) {
-                            mDisplayInfo.copyFrom(newInfo);
-                            mListener.onDisplayChanged(msg.arg1);
+                        if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) {
+                            if (extraLogging()) {
+                                Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
+                                        + info);
+                            }
+                            mDisplayInfo.copyFrom(info);
+                            mListener.onDisplayChanged(displayId);
                         }
                     }
                     break;
                 case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) {
-                        mListener.onDisplayChanged(msg.arg1);
+                        mListener.onDisplayChanged(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_REMOVED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
-                        mListener.onDisplayRemoved(msg.arg1);
+                        mListener.onDisplayRemoved(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) {
-                        mListener.onDisplayChanged(msg.arg1);
+                        mListener.onDisplayChanged(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_CONNECTED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
-                        mListener.onDisplayConnected(msg.arg1);
+                        mListener.onDisplayConnected(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_DISCONNECTED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
-                        mListener.onDisplayDisconnected(msg.arg1);
+                        mListener.onDisplayDisconnected(displayId);
                     }
                     break;
             }
@@ -1228,6 +1297,11 @@
                 Trace.endSection();
             }
         }
+
+        @Override
+        public String toString() {
+            return "mask: {" + mEventsMask + "}, for " + mListener.getClass();
+        }
     }
 
     /**
@@ -1353,4 +1427,18 @@
         }
         return "UNKNOWN";
     }
+
+
+    private static boolean initExtraLogging() {
+        if (sCurrentPackageName == null) {
+            sCurrentPackageName = ActivityThread.currentPackageName();
+            sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
+                    && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
+        }
+        return sExtraDisplayListenerLogging;
+    }
+
+    private static boolean extraLogging() {
+        return sExtraDisplayListenerLogging;
+    }
 }
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0221296..02304b5 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -1074,6 +1074,14 @@
      */
     public interface FaceDetectionCallback {
         void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+        /**
+         * An error has occurred with face detection.
+         *
+         * This callback signifies that this operation has been completed, and
+         * no more callbacks should be expected.
+         */
+        default void onDetectionError(int errorMsgId) {}
     }
 
     /**
@@ -1373,6 +1381,9 @@
         } else if (mRemovalCallback != null) {
             mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
                     getErrorString(mContext, errMsgId, vendorCode));
+        } else if (mFaceDetectionCallback != null) {
+            mFaceDetectionCallback.onDetectionError(errMsgId);
+            mFaceDetectionCallback = null;
         }
     }
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5bfda70..935157a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -462,6 +462,14 @@
          * Invoked when a fingerprint has been detected.
          */
         void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+        /**
+         * An error has occurred with fingerprint detection.
+         *
+         * This callback signifies that this operation has been completed, and
+         * no more callbacks should be expected.
+         */
+        default void onDetectionError(int errorMsgId) {}
     }
 
     /**
@@ -1484,6 +1492,9 @@
                     ? mRemoveTracker.mSingleFingerprint : null;
             mRemovalCallback.onRemovalError(fp, clientErrMsgId,
                     getErrorString(mContext, errMsgId, vendorCode));
+        } else if (mFingerprintDetectionCallback != null) {
+            mFingerprintDetectionCallback.onDetectionError(errMsgId);
+            mFingerprintDetectionCallback = null;
         }
     }
 
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
new file mode 100644
index 0000000..c6a352e
--- /dev/null
+++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.flags"
+
+flag {
+    name: "overlayproperties_class_api"
+    namespace: "core_graphics"
+    description: "public OverlayProperties class, OverlayProperties#supportMixedColorSpaces and Display#getOverlaySupport API"
+    bug: "267234573"
+}
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 861e440..6952e5d 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -2,5 +2,4 @@
 
 include /services/core/java/com/android/server/display/OWNERS
 
-marvinramin@google.com
-lcnathalie@google.com
+quxiangfang@google.com
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index aa55e54..05024ea 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -644,7 +644,7 @@
         }
 
         @Override
-        protected boolean initDataInjectionImpl(boolean enable) {
+        protected boolean initDataInjectionImpl(boolean enable, int mode) {
             return false;
         }
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ff1a6ac..7b8419e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1373,7 +1373,7 @@
     public interface InputDeviceListener {
         /**
          * Called whenever an input device has been added to the system.
-         * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
+         * Use {@link #getInputDevice(int)} to get more information about the device.
          *
          * @param deviceId The id of the input device that was added.
          */
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 4f07acf..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -17,6 +17,7 @@
 package android.hardware.radio;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -45,7 +46,7 @@
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private final Map<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+    private final ArrayMap<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier,
             RadioManager.ProgramInfo>> mPrograms = new ArrayMap<>();
 
     @GuardedBy("mLock")
@@ -203,11 +204,11 @@
             listCallbacksCopied = new ArrayList<>(mListCallbacks);
 
             if (chunk.isPurge()) {
-                Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
-                        RadioManager.ProgramInfo>>> programsIterator =
-                        mPrograms.entrySet().iterator();
+                Iterator<Map.Entry<ProgramSelector.Identifier,
+                        ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>>>
+                        programsIterator = mPrograms.entrySet().iterator();
                 while (programsIterator.hasNext()) {
-                    Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+                    Map.Entry<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier,
                             RadioManager.ProgramInfo>> removed = programsIterator.next();
                     if (removed.getValue() != null) {
                         removedList.add(removed.getKey());
@@ -270,8 +271,7 @@
         if (!mPrograms.containsKey(primaryKey)) {
             return;
         }
-        Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms
-                .get(primaryKey);
+        Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms.get(primaryKey);
         RadioManager.ProgramInfo removed = entries.remove(Objects.requireNonNull(key));
         if (removed == null) return;
         if (entries.size() == 0) {
@@ -287,15 +287,10 @@
     public @NonNull List<RadioManager.ProgramInfo> toList() {
         List<RadioManager.ProgramInfo> list = new ArrayList<>();
         synchronized (mLock) {
-            Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
-                    RadioManager.ProgramInfo>>> listIterator = mPrograms.entrySet().iterator();
-            while (listIterator.hasNext()) {
-                Iterator<Map.Entry<UniqueProgramIdentifier,
-                        RadioManager.ProgramInfo>> prorgramsIterator = listIterator.next()
-                        .getValue().entrySet().iterator();
-                while (prorgramsIterator.hasNext()) {
-                    list.add(prorgramsIterator.next().getValue());
-                }
+            for (int index = 0; index < mPrograms.size(); index++) {
+                ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries =
+                        mPrograms.valueAt(index);
+                list.addAll(entries.values());
             }
         }
         return list;
@@ -304,9 +299,16 @@
     /**
      * Returns the program with a specified primary identifier.
      *
+     * <p>This method only returns the first program from the list return from
+     * {@link #getProgramInfos}
+     *
      * @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) {
@@ -320,6 +322,29 @@
     }
 
     /**
+     * Returns the program list with a specified primary identifier.
+     *
+     * @param id primary identifier of a program to fetch
+     * @return the program info list with the primary identifier, or empty list if there is no such
+     * program identifier on the list
+     * @throws NullPointerException if primary identifier is {@code null}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public @NonNull List<RadioManager.ProgramInfo> getProgramInfos(
+            @NonNull ProgramSelector.Identifier id) {
+        Objects.requireNonNull(id, "Primary identifier can not be null");
+        ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
+        synchronized (mLock) {
+            entries = mPrograms.get(id);
+        }
+
+        if (entries == null) {
+            return new ArrayList<>();
+        }
+        return new ArrayList<>(entries.values());
+    }
+
+    /**
      * Filter for the program list.
      */
     public static final class Filter implements Parcelable {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 12442ba..c7ec052 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -16,6 +16,7 @@
 
 package android.hardware.radio;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -124,6 +125,86 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProgramType {}
 
+    /**
+     * Bitmask for HD radio subchannel 1
+     *
+     * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is
+     * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7 are
+     * indexes of additional supplemental program services (SPS).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_1 = 1 << 0;
+
+    /**
+     * Bitmask for HD radio subchannel 2
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_2 = 1 << 1;
+
+    /**
+     * Bitmask for HD radio subchannel 3
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_3 = 1 << 2;
+
+    /**
+     * Bitmask for HD radio subchannel 4
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_4 = 1 << 3;
+
+    /**
+     * Bitmask for HD radio subchannel 5
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_5 = 1 << 4;
+
+    /**
+     * Bitmask for HD radio subchannel 6
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_6 = 1 << 5;
+
+    /**
+     * Bitmask for HD radio subchannel 7
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_7 = 1 << 6;
+
+    /**
+     * Bitmask for HD radio subchannel 8
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_8 = 1 << 7;
+
+    /** @hide */
+    @IntDef(prefix = { "SUB_CHANNEL_HD_" }, value = {
+            SUB_CHANNEL_HD_1,
+            SUB_CHANNEL_HD_2,
+            SUB_CHANNEL_HD_3,
+            SUB_CHANNEL_HD_4,
+            SUB_CHANNEL_HD_5,
+            SUB_CHANNEL_HD_6,
+            SUB_CHANNEL_HD_7,
+            SUB_CHANNEL_HD_8,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HdSubChannel {}
+
     public static final int IDENTIFIER_TYPE_INVALID = 0;
     /**
      * Primary identifier for analog (without RDS) AM/FM stations:
@@ -147,19 +228,15 @@
      *
      * <p>Consists of (from the LSB):
      * <li>
-     *     <ul>32bit: Station ID number.
-     *     <ul>4bit: HD_SUBCHANNEL.
-     *     <ul>18bit: AMFM_FREQUENCY.
+     *     <ul>32bit: Station ID number.</ul>
+     *     <ul>4bit: HD subchannel, see {@link #SUB_CHANNEL_HD_1}.</ul>
+     *     <ul>18bit: AMFM_FREQUENCY.</ul>
      * </li>
      *
      * <p>While station ID number should be unique globally, it sometimes gets
      * abused by broadcasters (i.e. not being set at all). To ensure local
      * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
-     * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}.
-     *
-     * <p>HD Radio subchannel is a value in range of 0-7.
-     * This index is 0-based (where 0 is MPS and 1..7 are SPS),
-     * as opposed to HD Radio standard (where it's 1-based).
+     * a best-effort - see {@link #IDENTIFIER_TYPE_HD_STATION_NAME}.
      *
      * <p>The remaining bits should be set to zeros when writing on the chip side
      * and ignored when read.
@@ -202,9 +279,9 @@
      *
      * <p>Consists of (from the LSB):
      * <li>
-     *     <ul>16bit: SId.
-     *     <ul>8bit: ECC code.
-     *     <ul>4bit: SCIdS.
+     *     <ul>16bit: SId.</ul>
+     *     <ul>8bit: ECC code.</ul>
+     *     <ul>4bit: SCIdS.</ul>
      * </li>
      *
      * <p>SCIdS (Service Component Identifier within the Service) value
@@ -238,18 +315,26 @@
     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 */
+    /**
+     * 0-999 range
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
+     */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
      * 44bit compound primary identifier for Digital Audio Broadcasting and
      * Digital Multimedia Broadcasting.
      *
      * <p>Consists of (from the LSB):
-     * - 32bit: SId;
-     * - 8bit: ECC code;
-     * - 4bit: SCIdS.
+     * <li>
+     *     <ul>32bit: SId;</ul>
+     *     <ul>8bit: ECC code;</ul>
+     *     <ul>4bit: SCIdS.</ul>
+     * </li>
      *
      * <p>SCIdS (Service Component Identifier within the Service) value
      * of 0 represents the main service, while 1 and above represents
@@ -260,6 +345,36 @@
      */
     public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
     /**
+     * 64bit additional identifier for HD Radio representing station location.
+     *
+     * <p>Consists of (from the LSB):
+     * <li>
+     *     <ul>4 bit: Bits 0:3 of altitude</ul>
+     *     <ul>13 bit: Fractional bits of longitude</ul>
+     *     <ul>8 bit: Integer bits of longitude</ul>
+     *     <ul>1 bit: 0 for east and 1 for west for longitude</ul>
+     *     <ul>1 bit: 0, representing longitude</ul>
+     *     <ul>5 bit: pad of zeros separating longitude and latitude</ul>
+     *     <ul>4 bit: Bits 4:7 of altitude</ul>
+     *     <ul>13 bit: Fractional bits of latitude</ul>
+     *     <ul>8 bit: Integer bits of latitude</ul>
+     *     <ul>1 bit: 0 for north and 1 for south for latitude</ul>
+     *     <ul>1 bit: 1, representing latitude</ul>
+     *     <ul>5 bit: pad of zeros</ul>
+     * </li>
+     *
+     * <p>This format is defined in NRSC-5-C document: SY_IDD_1020s.
+     *
+     * <p>Due to Station ID abuse, some
+     * {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} identifiers may be not
+     * globally unique. To provide a best-effort solution, the station’s
+     * broadcast antenna containing the latitude and longitude may be
+     * carried as additional identifier and may be used by the tuner hardware
+     * to double-check tuning.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15;
+    /**
      * Primary identifier for vendor-specific radio technology.
      * The value format is determined by a vendor.
      *
@@ -300,6 +415,7 @@
         IDENTIFIER_TYPE_SXM_SERVICE_ID,
         IDENTIFIER_TYPE_SXM_CHANNEL,
         IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+        IDENTIFIER_TYPE_HD_STATION_LOCATION,
     })
     @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
     @Retention(RetentionPolicy.SOURCE)
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8c6083c..237ec01 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.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.Nullable;
@@ -159,12 +160,17 @@
     /**
      * Forces the analog playback for the supporting radio technology.
      *
-     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
-     * this option. This is purely user choice, ie. does not reflect digital-
+     * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+     * this option. This is purely user choice, i.e. does not reflect digital-
      * analog handover state managed from the HAL implementation side.
      *
-     * Some radio technologies may not support this, ie. DAB.
+     * <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.
@@ -199,6 +205,30 @@
     /** Enables DAB-FM soft-linking (related content). */
     public static final int CONFIG_DAB_FM_SOFT_LINKING = 9;
 
+    /**
+     * Forces the FM analog playback for the supporting radio technology.
+     *
+     * <p>User may disable FM digital playback for FM HD Radio or hybrid FM/DAB
+     * with this option. This is purely user choice, i.e. does not reflect
+     * digital-analog handover state managed from the HAL implementation side.
+     *
+     * <p>Some radio technologies may not support this, i.e. DAB.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int CONFIG_FORCE_ANALOG_FM = 10;
+
+    /**
+     * Forces the AM analog playback for the supporting radio technology.
+     *
+     * <p>User may disable FM digital playback for AM HD Radio or hybrid AM/DAB
+     * with this option. This is purely user choice, i.e. does not reflect
+     * digital-analog handover state managed from the HAL implementation side.
+     *
+     * <p>Some radio technologies may not support this, i.e. DAB.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int CONFIG_FORCE_ANALOG_AM = 11;
+
     /** @hide */
     @IntDef(prefix = { "CONFIG_" }, value = {
         CONFIG_FORCE_MONO,
@@ -210,6 +240,8 @@
         CONFIG_DAB_FM_LINKING,
         CONFIG_DAB_DAB_SOFT_LINKING,
         CONFIG_DAB_FM_SOFT_LINKING,
+        CONFIG_FORCE_ANALOG_FM,
+        CONFIG_FORCE_ANALOG_AM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConfigFlag {}
@@ -1441,6 +1473,9 @@
         private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
         private static final int FLAG_TUNED = 1 << 4;
         private static final int FLAG_STEREO = 1 << 5;
+        private static final int FLAG_SIGNAL_ACQUIRED = 1 << 6;
+        private static final int FLAG_HD_SIS_ACQUIRED = 1 << 7;
+        private static final int FLAG_HD_AUDIO_ACQUIRED = 1 << 8;
 
         @NonNull private final ProgramSelector mSelector;
         @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo;
@@ -1595,7 +1630,7 @@
         }
 
         /**
-         * {@code true} if radio stream is not playing, ie. due to bad reception
+         * {@code true} if radio stream is not playing, i.e. due to bad reception
          * conditions or buffering. In this state volume knob MAY be disabled to
          * prevent user increasing volume too much.
          * It does NOT mean the user has muted audio.
@@ -1621,6 +1656,28 @@
         }
 
         /**
+         * @return {@code true} if the signal has been acquired.
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        public boolean isSignalAcquired() {
+            return (mInfoFlags & FLAG_SIGNAL_ACQUIRED) != 0;
+        }
+        /**
+         * @return {@code true} if HD Station Information Service (SIS) information is available.
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        public boolean isHdSisAvailable() {
+            return (mInfoFlags & FLAG_HD_SIS_ACQUIRED) != 0;
+        }
+        /**
+         * @return {@code true} if HD audio is available.
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        public boolean isHdAudioAvailable() {
+            return (mInfoFlags & FLAG_HD_AUDIO_ACQUIRED) != 0;
+        }
+
+        /**
          * Signal quality (as opposed to the name) indication from 0 (no signal)
          * to 100 (excellent)
          * @return the signal quality indication.
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index b7bf783..db14c08 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.radio;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -30,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -142,12 +144,84 @@
     public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT =
             "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
 
+    /**
+     * Short context description of comment
+     *
+     * <p>Comment could relate to the current audio program content, or it might
+     * be unrelated information that the station chooses to send. It is composed
+     * of short content description and actual text (see NRSC-G200-A and id3v2.3.0
+     * for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION =
+            "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION";
+
+    /**
+     * Actual text of comment
+     *
+     * @see #METADATA_KEY_COMMENT_SHORT_DESCRIPTION
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT =
+            "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT";
+
+    /**
+     * Commercial
+     *
+     * <p>Commercial is application specific and generally used to facilitate the
+     * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_COMMERCIAL =
+            "android.hardware.radio.metadata.COMMERCIAL";
+
+    /**
+     * Array of Unique File Identifiers
+     *
+     * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric
+     * identifier of the current content, or of an advertised product or
+     * service (see NRSC-G200-A and id3v2.3.0 for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS";
+
+    /**
+     * HD short station name or HD universal short station name
+     *
+     * <p>It can be up to 12 characters (see SY_IDD_1020s for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_HD_STATION_NAME_SHORT =
+            "android.hardware.radio.metadata.HD_STATION_NAME_SHORT";
+
+    /**
+     * HD long station name, HD station slogan or HD station message
+     *
+     * <p>(see SY_IDD_1020s for more info)
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_HD_STATION_NAME_LONG =
+            "android.hardware.radio.metadata.HD_STATION_NAME_LONG";
+
+    /**
+     * Bit mask of all HD Radio subchannels available
+     *
+     * <p>Bit {@link ProgramSelector#SUB_CHANNEL_HD_1} from LSB represents the
+     * availability of HD-1 subchannel (main program service, MPS). Bits
+     * {@link ProgramSelector#SUB_CHANNEL_HD_2} to {@link ProgramSelector#SUB_CHANNEL_HD_8}
+     * from LSB represent HD-2 to HD-8 subchannel (supplemental program services, SPS)
+     * respectively.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE =
+            "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE";
 
     private static final int METADATA_TYPE_INVALID = -1;
     private static final int METADATA_TYPE_INT = 0;
     private static final int METADATA_TYPE_TEXT = 1;
     private static final int METADATA_TYPE_BITMAP = 2;
     private static final int METADATA_TYPE_CLOCK = 3;
+    private static final int METADATA_TYPE_TEXT_ARRAY = 4;
 
     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
 
@@ -172,6 +246,13 @@
         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT);
     }
 
     // keep in sync with: system/media/radio/include/system/radio_metadata.h
@@ -288,9 +369,12 @@
             return false;
         }
         for (String key : mBundle.keySet()) {
-            // This logic will return a false negative if we ever put Bundles into mBundle. As of
-            // 2019-04-09, we only put ints, Strings, and Parcelables in, so it's fine for now.
-            if (!mBundle.get(key).equals(otherBundle.get(key))) {
+            if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key),
+                    METADATA_TYPE_TEXT_ARRAY)) {
+                if (!Arrays.equals(mBundle.getStringArray(key), otherBundle.getStringArray(key))) {
+                    return false;
+                }
+            } else if (!Objects.equals(mBundle.get(key), otherBundle.get(key))) {
                 return false;
             }
         }
@@ -326,7 +410,21 @@
 
             sb.append(keyDisp);
             sb.append('=');
-            sb.append(mBundle.get(key));
+            if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key),
+                    METADATA_TYPE_TEXT_ARRAY)) {
+                String[] stringArrayValue = mBundle.getStringArray(key);
+                sb.append('[');
+                for (int i = 0; i < stringArrayValue.length; i++) {
+                    if (i != 0) {
+                        sb.append(',');
+                    }
+                    sb.append(stringArrayValue[i]);
+                }
+                sb.append(']');
+            } else {
+                sb.append(mBundle.get(key));
+            }
+
         }
 
         sb.append("]");
@@ -427,6 +525,36 @@
         return clock;
     }
 
+    /**
+     * Gets the string array value associated with the given key as a string
+     * array.
+     *
+     * <p>Only string array keys may be used with this method:
+     * <ul>
+     * <li>{@link #METADATA_KEY_UFIDS}</li>
+     * </ul>
+     *
+     * @param key The key the value is stored under
+     * @return String array of the given string-array-type key
+     * @throws NullPointerException if metadata key is {@code null}
+     * @throws IllegalArgumentException if the metadata with the key is not found in
+     * metadata or the key is not of string-array type
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    @NonNull
+    public String[] getStringArray(@NonNull String key) {
+        Objects.requireNonNull(key, "Metadata key can not be null");
+        if (!Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) {
+            throw new IllegalArgumentException("Failed to retrieve key " + key
+                    + " as string array");
+        }
+        String[] stringArrayValue = mBundle.getStringArray(key);
+        if (stringArrayValue == null) {
+            throw new IllegalArgumentException("Key " + key + " is not found in metadata");
+        }
+        return stringArrayValue;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -539,6 +667,11 @@
          * <li>{@link #METADATA_KEY_ARTIST}</li>
          * <li>{@link #METADATA_KEY_ALBUM}</li>
          * <li>{@link #METADATA_KEY_GENRE}</li>
+         * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li>
+         * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li>
+         * <li>{@link #METADATA_KEY_COMMERCIAL}</li>
+         * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li>
+         * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li>
          * </ul>
          *
          * @param key The key for referencing this value
@@ -563,6 +696,7 @@
          * <li>{@link #METADATA_KEY_RDS_PI}</li>
          * <li>{@link #METADATA_KEY_RDS_PTY}</li>
          * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
+         * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li>
          * </ul>
          * or any bitmap represented by its identifier.
          *
@@ -621,6 +755,35 @@
         }
 
         /**
+         * Put a String array into the meta data. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_UFIDS}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return the same Builder instance
+         * @throws NullPointerException if key or value is null
+         * @throws IllegalArgumentException if the key is not string-array-type key
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        @NonNull
+        public Builder putStringArray(@NonNull String key, @NonNull String[] value) {
+            Objects.requireNonNull(key, "Key can not be null");
+            Objects.requireNonNull(value, "Value can not be null");
+            if (!METADATA_KEYS_TYPE.containsKey(key)
+                    || !Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) {
+                throw new IllegalArgumentException("The " + key
+                        + " key cannot be used to put a RadioMetadata String Array.");
+            }
+            mBundle.putStringArray(key, value);
+            return this;
+        }
+
+
+        /**
          * Creates a {@link RadioMetadata} instance with the specified fields.
          *
          * @return a new {@link RadioMetadata} object
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index bdbca91..ba31ca3 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -363,7 +363,7 @@
     @Override
     public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
         try {
-            return mTuner.isConfigFlagSet(flag);
+            return mTuner.isConfigFlagSet(convertForceAnalogConfigFlag(flag));
         } catch (RemoteException e) {
             throw new RuntimeException("Service died", e);
         }
@@ -372,7 +372,7 @@
     @Override
     public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
         try {
-            mTuner.setConfigFlag(flag, value);
+            mTuner.setConfigFlag(convertForceAnalogConfigFlag(flag), value);
         } catch (RemoteException e) {
             throw new RuntimeException("Service died", e);
         }
@@ -411,4 +411,13 @@
             return false;
         }
     }
+
+    private @RadioManager.ConfigFlag int convertForceAnalogConfigFlag(
+            @RadioManager.ConfigFlag int flag) throws RemoteException {
+        if (Flags.hdRadioImproved() && flag == RadioManager.CONFIG_FORCE_ANALOG
+                && mTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) {
+            flag = RadioManager.CONFIG_FORCE_ANALOG_FM;
+        }
+        return flag;
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 582c5c0..ba80811 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3231,7 +3231,7 @@
     }
 
     /**
-     * Called when the user tapped or clicked an {@link android.widget.Editor}.
+     * Called when the user tapped or clicked an editor.
      * This can be useful when IME makes a decision of showing Virtual keyboard based on what
      * {@link MotionEvent#getToolType(int)} was used to click the editor.
      * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
@@ -3363,6 +3363,13 @@
                 return true;
             }
             return false;
+        } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+                event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
+            if (mDecorViewVisible && mWindowVisible) {
+                int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+                mPrivOps.switchKeyboardLayoutAsync(direction);
+                return true;
+            }
         }
         return doMovementKey(keyCode, event, MOVEMENT_DOWN);
     }
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
new file mode 100644
index 0000000..6956916
--- /dev/null
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.net.vcn"
+
+flag {
+    name: "safe_mode_config"
+    namespace: "vcn"
+    description: "Feature flag for safe mode configurability"
+    bug: "276358140"
+}
\ No newline at end of file
diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 0000000..f768330
--- /dev/null
+++ b/core/java/android/nfc/Constants.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.nfc;
+
+/**
+ * @hide
+ * TODO(b/303286040): Holds @hide API constants. Formalize these APIs.
+ */
+public final class Constants {
+    private Constants() { }
+
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground";
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+    public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index a6d8caf..0c95c2e 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -79,4 +79,9 @@
     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);
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 1307dfc..4a7bd3f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -17,12 +17,14 @@
 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;
@@ -36,6 +38,7 @@
 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;
@@ -1593,6 +1596,40 @@
         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 (NfcAdapter.class) {
+            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);
+        }
+    }
+
     /**
      * Manually invoke Android Beam to share data.
      *
@@ -1826,6 +1863,97 @@
     }
 
     /**
+     * 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
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index 958669e..ae3e333 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -34,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.regex.Pattern;
 
 /**********************************************************************
  * This file is not a part of the NFC mainline module                 *
@@ -79,7 +80,7 @@
             throw new IllegalArgumentException("Too many AIDs in AID group.");
         }
         for (String aid : aids) {
-            if (!CardEmulation.isValidAid(aid)) {
+            if (!isValidAid(aid)) {
                 throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
             }
         }
@@ -264,4 +265,34 @@
         return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
                 CardEmulation.CATEGORY_OTHER.equals(category);
     }
+
+    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/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 18ec914..665b753 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Class holding APDU service info.
@@ -307,7 +308,7 @@
                             com.android.internal.R.styleable.AidFilter);
                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
                             toUpperCase();
-                    if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
                         currentGroup.getAids().add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -321,7 +322,7 @@
                             toUpperCase();
                     // Add wildcard char to indicate prefix
                     aid = aid.concat("*");
-                    if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
                         currentGroup.getAids().add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -335,7 +336,7 @@
                             toUpperCase();
                     // Add wildcard char to indicate suffix
                     aid = aid.concat("#");
-                    if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
                         currentGroup.getAids().add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -806,7 +807,7 @@
      */
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public void dumpDebug(@NonNull ProtoOutputStream proto) {
-        Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME);
+        getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
         proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
         proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
         if (!mOnHost) {
@@ -825,4 +826,34 @@
         }
         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
index 4909b08..32c2a1b 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.Constants;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
@@ -274,7 +275,7 @@
             try {
                 preferForeground = Settings.Secure.getInt(
                         contextAsUser.getContentResolver(),
-                        Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+                        Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
             } catch (SettingNotFoundException e) {
             }
             return preferForeground;
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index ec919e4..33bc169 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -173,7 +173,7 @@
                             com.android.internal.R.styleable.SystemCodeFilter);
                     systemCode = a.getString(
                             com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
-                    if (!NfcFCardEmulation.isValidSystemCode(systemCode) &&
+                    if (!isValidSystemCode(systemCode) &&
                             !systemCode.equalsIgnoreCase("NULL")) {
                         Log.e(TAG, "Invalid System Code: " + systemCode);
                         systemCode = null;
@@ -187,7 +187,7 @@
                             com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
                     if (!nfcid2.equalsIgnoreCase("RANDOM") &&
                             !nfcid2.equalsIgnoreCase("NULL") &&
-                            !NfcFCardEmulation.isValidNfcid2(nfcid2)) {
+                            !isValidNfcid2(nfcid2)) {
                         Log.e(TAG, "Invalid NFCID2: " + nfcid2);
                         nfcid2 = null;
                     }
@@ -436,10 +436,62 @@
      */
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public void dumpDebug(@NonNull ProtoOutputStream proto) {
-        Utils.dumpDebugComponentName(getComponent(), proto, NfcFServiceInfoProto.COMPONENT_NAME);
+        getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
         proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
         proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
         proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
         proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
     }
+
+    /**
+     * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+     * @hide
+     */
+    private static boolean isValidSystemCode(String systemCode) {
+        if (systemCode == null) {
+            return false;
+        }
+        if (systemCode.length() != 4) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+        if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        try {
+            Integer.parseInt(systemCode, 16);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+     * @hide
+     */
+    private static boolean isValidNfcid2(String nfcid2) {
+        if (nfcid2 == null) {
+            return false;
+        }
+        if (nfcid2.length() != 16) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        // check if the the value starts with "02FE"
+        if (!nfcid2.toUpperCase().startsWith("02FE")) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        try {
+            Long.parseLong(nfcid2, 16);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index e3faf39..cd50ace 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -6,3 +6,17 @@
     description: "Flag for NFC mainline changes"
     bug: "292140387"
 }
+
+flag {
+    name: "enable_nfc_reader_option"
+    namespace: "nfc"
+    description: "Flag for NFC reader option API changes"
+    bug: "291187960"
+}
+
+flag {
+    name: "enable_nfc_user_restriction"
+    namespace: "nfc"
+    description: "Flag for NFC user restriction"
+    bug: "291187960"
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 092923e..6a4ec9b 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,7 +16,10 @@
 
 package android.os;
 
+import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+
 import android.Manifest.permission;
+import android.annotation.FlaggedApi;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -354,17 +357,11 @@
     public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9;
 
     /**
-     *
-     * Percentage representing the measured battery state of health (remaining
-     * estimated full charge capacity relative to the rated capacity in %).
-     *
-     * <p class="note">
-     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
-     *
-     * @hide
+     * Percentage representing the measured battery state of health.
+     * This is the remaining estimated full charge capacity relative
+     * to the rated capacity in %.
      */
-    @RequiresPermission(permission.BATTERY_STATS)
-    @SystemApi
+    @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
     public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
 
     private final Context mContext;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8482945..e85b7bf 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1854,7 +1854,7 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
         public HistoryItem next;
 
-        // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
+        // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().
         @UnsupportedAppUsage
         public long time;
 
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 955fad3..3abe9a0 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -520,8 +520,9 @@
      * @param uid calling package uid
      * @param reason why Bluetooth has been turned on
      * @param packageName package responsible for this change
-     * @Deprecated Bluetooth self report its state and no longer call this
+     * @deprecated Bluetooth self report its state and no longer call this
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
     }
@@ -532,8 +533,9 @@
      * @param uid calling package uid
      * @param reason why Bluetooth has been turned on
      * @param packageName package responsible for this change
-     * @Deprecated Bluetooth self report its state and no longer call this
+     * @deprecated Bluetooth self report its state and no longer call this
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
     }
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 49d7e8b..32840d4 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -300,6 +300,7 @@
          * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
          * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
          */
+        // TODO(b/298459065): switch to monotonic clock
         public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
             mFromTimestamp = fromTimestamp;
             mToTimestamp = toTimestamp;
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 086b0e5..960e84d 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -26,6 +26,7 @@
 import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
 import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -143,10 +144,11 @@
          */
         public void onError(@BugreportErrorCode int errorCode) {}
 
-        /** Called when taking bugreport finishes successfully.
+        /**
+         * Called when taking bugreport finishes successfully.
          *
          * <p>This callback will be invoked if the
-         * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
+         * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
          */
         public void onFinished() {}
 
@@ -279,8 +281,8 @@
      *
      * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
      *
-     * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
-     * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
+     * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not
+     * consented on previous attempts.
      *
      * @param bugreportFile the identifier for a bugreport that was previously generated for this
      *      caller using {@code startBugreport}.
@@ -293,6 +295,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.DUMP)
     @WorkerThread
+    @UserHandleAware
     public void retrieveBugreport(
             @NonNull String bugreportFile,
             @NonNull ParcelFileDescriptor bugreportFd,
@@ -306,8 +309,10 @@
             Preconditions.checkNotNull(callback);
             DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
             mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
+                    mContext.getUserId(),
                     bugreportFd.getFileDescriptor(),
                     bugreportFile,
+                    /* keepBugreportOnRetrieval = */ false,
                     dsListener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 47ad72f..8510084 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -16,9 +16,11 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -127,12 +129,15 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
     public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;
 
     /**
      * The maximum value of supported bugreport mode.
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE)
     @TestApi
     public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;
 
@@ -143,7 +148,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
             BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
-            BUGREPORT_FLAG_DEFER_CONSENT
+            BUGREPORT_FLAG_DEFER_CONSENT,
+            BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
     })
     public @interface BugreportFlag {}
 
@@ -163,4 +169,20 @@
      * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
      */
     public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
+
+    /**
+     * Flag for keeping a bugreport stored even after it has been retrieved via
+     * {@link BugreportManager#retrieveBugreport}.
+     *
+     * <p>This flag can only be used when {@link #BUGREPORT_FLAG_DEFER_CONSENT} is set.
+     * The bugreport may be retrieved multiple times using
+     * {@link BugreportManager#retrieveBugreport(
+     * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+    public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
+            IDumpstate.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL;
 }
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index cf35460..a1d2dcc 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -17,7 +17,7 @@
 package android.os;
 
 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
-import static android.Manifest.permission.READ_LOGS;
+import static android.Manifest.permission.READ_DROPBOX_DATA;
 
 import android.annotation.BytesLong;
 import android.annotation.CurrentTimeMillisLong;
@@ -81,9 +81,12 @@
 
     /**
      * Broadcast Action: This is broadcast when a new entry is added in the dropbox.
-     * You must hold the {@link android.Manifest.permission#READ_LOGS} permission
-     * in order to receive this broadcast. This broadcast can be rate limited for low priority
-     * entries
+     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and later, you
+     * must hold the {@link android.Manifest.permission#READ_DROPBOX_DATA} permission
+     * in order to receive this broadcast. For apps targeting Android versions lower
+     * than {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, you must hold
+     * {@link android.Manifest.permission#READ_LOGS}.
+     * This broadcast can be rate limited for low priority entries
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
@@ -382,12 +385,17 @@
     /**
      * Gets the next entry from the drop box <em>after</em> the specified time.
      * You must always call {@link Entry#close()} on the return value!
+     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is
+     * required for apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+     * and later. {@link android.Manifest.permission#READ_LOGS} permission is
+     * required for apps targeting Android versions lower than
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
      *
      * @param tag of entry to look for, null for all tags
      * @param msec time of the last entry seen
      * @return the next entry, or null if there are no more entries
      */
-    @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS })
+    @RequiresPermission(allOf = { READ_DROPBOX_DATA, PACKAGE_USAGE_STATS })
     public @Nullable Entry getNextEntry(String tag, long msec) {
         try {
             return mService.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(),
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..d7e440b 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@
 
     /**
      * Find the intersection between this LocaleList and another
-     * @return a String array of the Locales in both LocaleLists
+     * @return an array of the Locales in both LocaleLists
      * {@hide}
      */
     @NonNull
-    public String[] getIntersection(@NonNull LocaleList other) {
-        List<String> intersection = new ArrayList<>();
+    public Locale[] getIntersection(@NonNull LocaleList other) {
+        List<Locale> intersection = new ArrayList<>();
         for (Locale l1 : mList) {
             for (Locale l2 : other.mList) {
                 if (matchesLanguageAndScript(l2, l1)) {
-                    intersection.add(l1.toLanguageTag());
+                    intersection.add(l1);
                     break;
                 }
             }
         }
-        return intersection.toArray(new String[0]);
+        return intersection.toArray(new Locale[0]);
     }
 
     /**
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
index 151a65f..ca1d49a 100644
--- a/core/java/android/os/OomKillRecord.java
+++ b/core/java/android/os/OomKillRecord.java
@@ -15,10 +15,15 @@
  */
 package android.os;
 
+import com.android.internal.util.FrameworkStatsLog;
 
 /**
+ * Activity manager communication with kernel out-of-memory (OOM) data handling
+ * and statsd atom logging.
+ *
  * Expected data to get back from the OOM event's file.
- * Note that this should be equivalent to the struct <b>OomKill</b> inside
+ * Note that this class fields' should be equivalent to the struct
+ * <b>OomKill</b> inside
  * <pre>
  * system/memory/libmeminfo/libmemevents/include/memevents.h
  * </pre>
@@ -41,6 +46,18 @@
         this.mOomScoreAdj = oomScoreAdj;
     }
 
+    /**
+     * Logs the event when the kernel OOM killer claims a victims to reduce
+     * memory pressure.
+     * KernelOomKillOccurred = 754
+     */
+    public void logKillOccurred() {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED,
+                mUid, mPid, mOomScoreAdj, mTimeStampInMillis,
+                mProcessName);
+    }
+
     public long getTimestampMilli() {
         return mTimeStampInMillis;
     }
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index cbc9213..11084b8 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -231,6 +232,7 @@
          *
          * @param enabled The flag that sets whether this session uses power-efficient scheduling.
          */
+        @FlaggedApi(Flags.FLAG_ADPF_PREFER_POWER_EFFICIENCY)
         public void setPreferPowerEfficiency(boolean enabled) {
             nativeSetPreferPowerEfficiency(mNativeSessionPtr, enabled);
         }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4174c1c..d2c1755 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
@@ -1940,6 +1941,7 @@
      *
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API)
     @TestApi
     public boolean isBatterySaverSupported() {
         try {
@@ -2670,7 +2672,7 @@
 
         /**
          * Called when overall thermal throttling status changed.
-         * @param status defined in {@link android.os.Temperature}.
+         * @param status the status
          */
         void onThermalStatusChanged(@ThermalStatus int status);
     }
diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java
index 5fb0df7..8c5f2cd 100644
--- a/core/java/android/os/PowerMonitor.java
+++ b/core/java/android/os/PowerMonitor.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 
@@ -23,12 +24,19 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an
- * EnergyConsumer, as defined in
- * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a>
- *
- * @hide
+ * A PowerMonitor represents either an ODPM rail (on-device power rail monitor) or a modeled
+ * energy consumer.
+ * <p/>
+ * ODPM rail names are device-specific. No assumptions should be made about the names and
+ * exact purpose of ODPM rails across different device models. A rail name may be something
+ * like "S2S_VDD_G3D"; specific knowledge of the device hardware is required to interpret
+ * the corresponding power monitor data.
+ * <p/>
+ * Energy consumer have more human-readable names, e.g. "GPU", "MODEM" etc. However, developers
+ * must be extra cautious about using energy consumers across different device models,
+ * as their exact implementations are also hardware dependent and are customized by OEMs.
  */
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
 public final class PowerMonitor implements Parcelable {
 
     /**
@@ -36,9 +44,9 @@
      * power rail measurement, or modeled in some fashion.  For example, an energy consumer may
      * represent a combination of multiple rails or a portion of a rail shared between subsystems,
      * e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail.
-     * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType),
-     * others are not.
+     * Some consumer names are standardized, others are not.
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public static final int POWER_MONITOR_TYPE_CONSUMER = 0;
 
     /**
@@ -46,6 +54,7 @@
      * no assumptions can be made about the source of those measurements across different devices,
      * even if they have the same name.
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;
 
     /** @hide */
@@ -64,38 +73,60 @@
      * @hide
      */
     public final int index;
+
     @PowerMonitorType
-    public final int type;
+    private final int mType;
     @NonNull
-    public final String name;
+    private final String mName;
 
     /**
      * @hide
      */
     public PowerMonitor(int index, int type, @NonNull String name) {
         this.index = index;
-        this.type = type;
-        this.name = name;
+        this.mType = type;
+        this.mName = name;
+    }
+
+    /**
+     * Returns the type of the power monitor.
+     */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+    @PowerMonitorType
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the power monitor, either a power rail or an energy consumer.
+     */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+    @NonNull
+    public String getName() {
+        return mName;
     }
 
     private PowerMonitor(Parcel in) {
         index = in.readInt();
-        type = in.readInt();
-        name = in.readString();
+        mType = in.readInt();
+        mName = in.readString8();
     }
 
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(index);
-        dest.writeInt(type);
-        dest.writeString(name);
+        dest.writeInt(mType);
+        dest.writeString8(mName);
     }
 
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @Override
     public int describeContents() {
         return 0;
     }
 
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @NonNull
     public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
         @Override
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index e767059..bb677d5 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 
 import java.util.Arrays;
@@ -24,10 +25,10 @@
 
 /**
  * A collection of energy measurements from Power Monitors.
- *
- * @hide
  */
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
 public final class PowerMonitorReadings {
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public static final int ENERGY_UNAVAILABLE = -1;
 
     @NonNull
@@ -41,7 +42,7 @@
             Comparator.comparingInt(pm -> pm.index);
 
     /**
-     * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
+     * @param powerMonitors array of power monitor, sorted by PowerMonitor.index
      * @hide
      */
     public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
@@ -56,7 +57,8 @@
      * Does not persist across reboots.
      * Represents total energy: both on-battery and plugged-in.
      */
-    public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) {
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+    public long getConsumedEnergy(@NonNull PowerMonitor powerMonitor) {
         int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
         if (offset >= 0) {
             return mEnergyUws[offset];
@@ -67,6 +69,7 @@
     /**
      * Elapsed realtime, in milliseconds, when the snapshot was taken.
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @ElapsedRealtimeLong
     public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
         int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
@@ -84,7 +87,7 @@
             if (i != 0) {
                 sb.append(", ");
             }
-            sb.append(mPowerMonitors[i].name)
+            sb.append(mPowerMonitors[i].getName())
                     .append(" = ").append(mEnergyUws[i])
                     .append(" (").append(mTimestampsMs[i]).append(')');
         }
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 970f419..ace5f7e 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -66,7 +66,7 @@
     /**
      * Rethrow this exception when we know it came from the system server. This
      * gives us an opportunity to throw a nice clean
-     * {@link DeadSystemRuntimeException} signal to avoid spamming logs with
+     * {@code DeadSystemRuntimeException} signal to avoid spamming logs with
      * misleading stack traces.
      * <p>
      * Apps making calls into the system server may end up persisting internal
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 81d4e3a..47b6d8d 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2367,14 +2367,14 @@
     }
 
     /** Assume locked until we hear otherwise */
-    private static volatile boolean sUserKeyUnlocked = false;
+    private static volatile boolean sCeStorageUnlocked = false;
 
-    private static boolean isUserKeyUnlocked(int userId) {
+    private static boolean isCeStorageUnlocked(int userId) {
         final IStorageManager storage = IStorageManager.Stub
                 .asInterface(ServiceManager.getService("mount"));
         if (storage != null) {
             try {
-                return storage.isUserKeyUnlocked(userId);
+                return storage.isCeStorageUnlocked(userId);
             } catch (RemoteException ignored) {
             }
         }
@@ -2387,13 +2387,13 @@
         // since any relocking of that user will always result in our
         // process being killed to release any CE FDs we're holding onto.
         if (userId == UserHandle.myUserId()) {
-            if (sUserKeyUnlocked) {
+            if (sCeStorageUnlocked) {
                 return;
-            } else if (isUserKeyUnlocked(userId)) {
-                sUserKeyUnlocked = true;
+            } else if (isCeStorageUnlocked(userId)) {
+                sCeStorageUnlocked = true;
                 return;
             }
-        } else if (isUserKeyUnlocked(userId)) {
+        } else if (isCeStorageUnlocked(userId)) {
             return;
         }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4d385e4..cc79ae3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -59,6 +59,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
+import android.nfc.Flags;
 import android.provider.Settings;
 import android.util.AndroidException;
 import android.util.ArraySet;
@@ -1872,6 +1873,7 @@
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_USER_RESTRICTION)
     public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO =
             "no_near_field_communication_radio";
 
@@ -3367,7 +3369,10 @@
      *
      * @return whether the context user is running in the foreground.
      */
-    @UserHandleAware
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
     public boolean isUserForeground() {
         try {
             return mService.isUserForeground(mUserId);
@@ -3441,11 +3446,10 @@
      * @hide
      */
     @SystemApi
-    @UserHandleAware
-    @RequiresPermission(anyOf = {
-            "android.permission.INTERACT_ACROSS_USERS",
-            "android.permission.MANAGE_USERS"
-    })
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
     public boolean isUserVisible() {
         try {
             return mService.isUserVisible(mUserId);
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 98f9dff..5078dc35 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -140,6 +140,31 @@
      */
     public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA;
 
+    /** @hide */
+    @IntDef(prefix = { "CATEGORY_" }, value = {
+            CATEGORY_UNKNOWN,
+            CATEGORY_KEYBOARD,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Category {}
+
+    /**
+     * Category value when the vibration category is unknown.
+     *
+     * @hide
+     */
+    public static final int CATEGORY_UNKNOWN = 0x0;
+
+    /**
+     * Category value for keyboard vibrations.
+     *
+     * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key
+     * press/release, for example.
+     *
+     * @hide
+     */
+    public static final int CATEGORY_KEYBOARD = 1;
+
     /**
      * @hide
      */
@@ -147,7 +172,8 @@
             FLAG_BYPASS_INTERRUPTION_POLICY,
             FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
             FLAG_INVALIDATE_SETTINGS_CACHE,
-            FLAG_PIPELINED_EFFECT
+            FLAG_PIPELINED_EFFECT,
+            FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flag{}
@@ -167,6 +193,8 @@
      * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and
      * {@link AudioAttributes#FLAG_BYPASS_MUTE}.
      *
+     * <p>Only privileged apps can ignore user settings, and this flag will be ignored otherwise.
+     *
      * @hide
      */
     public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 1 << 1;
@@ -199,12 +227,31 @@
     public static final int FLAG_PIPELINED_EFFECT = 1 << 3;
 
     /**
+     * Flag requesting that this vibration effect to be played without applying the user
+     * intensity setting to scale the vibration.
+     *
+     * <p>The user setting is still applied to enable/disable the vibration, but the vibration
+     * effect strength will not be scaled based on the enabled setting value.
+     *
+     * <p>This is intended to be used on scenarios where the system needs to enforce a specific
+     * strength for the vibration effect, regardless of the user preference. Only privileged apps
+     * can ignore user settings, and this flag will be ignored otherwise.
+     *
+     * <p>If you need to bypass the user setting when it's disabling vibrations then this also
+     * needs the flag {@link #FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF} to be set.
+     *
+     * @hide
+     */
+    public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE = 1 << 4;
+
+    /**
      * All flags supported by vibrator service, update it when adding new flag.
      * @hide
      */
     public static final int FLAG_ALL_SUPPORTED =
             FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
-                    | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT;
+                    | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT
+                    | FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
 
     /** Creates a new {@link VibrationAttributes} instance with given usage. */
     public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
@@ -214,12 +261,14 @@
     private final int mUsage;
     private final int mFlags;
     private final int mOriginalAudioUsage;
+    private final int mCategory;
 
     private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
-            @Flag int flags) {
+            @Flag int flags, @Category int category) {
         mUsage = usage;
         mOriginalAudioUsage = audioUsage;
         mFlags = flags & FLAG_ALL_SUPPORTED;
+        mCategory = category;
     }
 
     /**
@@ -248,6 +297,20 @@
     }
 
     /**
+     * Return the vibration category.
+     *
+     * <p>Vibration categories describe the source of the vibration, and it can be combined with
+     * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and
+     * category keyboard can be used to control keyboard haptic feedback independently.
+     *
+     * @hide
+     */
+    @Category
+    public int getCategory() {
+        return mCategory;
+    }
+
+    /**
      * Check whether a flag is set
      * @return true if a flag is set and false otherwise
      */
@@ -298,12 +361,14 @@
         dest.writeInt(mUsage);
         dest.writeInt(mOriginalAudioUsage);
         dest.writeInt(mFlags);
+        dest.writeInt(mCategory);
     }
 
     private VibrationAttributes(Parcel src) {
         mUsage = src.readInt();
         mOriginalAudioUsage = src.readInt();
         mFlags = src.readInt();
+        mCategory = src.readInt();
     }
 
     public static final @NonNull Parcelable.Creator<VibrationAttributes>
@@ -326,12 +391,12 @@
         }
         VibrationAttributes rhs = (VibrationAttributes) o;
         return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage
-                && mFlags == rhs.mFlags;
+                && mFlags == rhs.mFlags && mCategory == rhs.mCategory;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUsage, mOriginalAudioUsage, mFlags);
+        return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory);
     }
 
     @Override
@@ -340,6 +405,7 @@
                 + "mUsage=" + usageToString()
                 + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
                 + ", mFlags=" + mFlags
+                + ", mCategory=" + categoryToString()
                 + '}';
     }
 
@@ -376,6 +442,23 @@
         }
     }
 
+    /** @hide */
+    public String categoryToString() {
+        return categoryToString(mCategory);
+    }
+
+    /** @hide */
+    public static String categoryToString(@Category int category) {
+        switch (category) {
+            case CATEGORY_UNKNOWN:
+                return "UNKNOWN";
+            case CATEGORY_KEYBOARD:
+                return "KEYBOARD";
+            default:
+                return "unknown category " + category;
+        }
+    }
+
     /**
      * Builder class for {@link VibrationAttributes} objects.
      * By default, all information is set to UNKNOWN.
@@ -384,6 +467,7 @@
         private int mUsage = USAGE_UNKNOWN;
         private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
         private int mFlags = 0x0;
+        private int mCategory = CATEGORY_UNKNOWN;
 
         /**
          * Constructs a new Builder with the defaults.
@@ -399,6 +483,7 @@
                 mUsage = vib.mUsage;
                 mOriginalAudioUsage = vib.mOriginalAudioUsage;
                 mFlags = vib.mFlags;
+                mCategory = vib.mCategory;
             }
         }
 
@@ -464,7 +549,8 @@
          * @return a new {@link VibrationAttributes} object
          */
         public @NonNull VibrationAttributes build() {
-            VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags);
+            VibrationAttributes ans = new VibrationAttributes(
+                    mUsage, mOriginalAudioUsage, mFlags, mCategory);
             return ans;
         }
 
@@ -480,6 +566,19 @@
         }
 
         /**
+         * Sets the attribute describing the category of the corresponding vibration.
+         *
+         * @param category The category for the vibration
+         * @return the same Builder instance.
+         *
+         * @hide
+         */
+        public @NonNull Builder setCategory(@Category int category) {
+            mCategory = category;
+            return this;
+        }
+
+        /**
          * Sets only the flags specified in the bitmask, leaving the other supported flag values
          * unchanged in the builder.
          *
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 99c9925..2fc2414 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,16 @@
     }
 
     /**
+     * Whether the keyboard vibration is enabled by default.
+     *
+     * @return {@code true} if the keyboard vibration is default enabled, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isDefaultKeyboardVibrationEnabled() {
+        return getConfig().isDefaultKeyboardVibrationEnabled();
+    }
+
+    /**
      * Return the ID of this vibrator.
      *
      * @return A non-negative integer representing the id of the vibrator controlled by this
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index a95e66d..86f03cd 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.os"
 
 flag {
+    name: "state_of_health_public"
+    namespace: "system_sw_battery"
+    description: "Feature flag for making state_of_health a public api."
+    bug: "288842045"
+}
+
+flag {
     name: "disallow_cellular_null_ciphers_restriction"
     namespace: "cellular_security"
     description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
@@ -9,7 +16,7 @@
 
 flag {
     name: "remove_app_profiler_pss_collection"
-    namespace: "android_platform_power_optimization"
+    namespace: "backstage_power"
     description: "Replaces background PSS collection in AppProfiler with RSS"
     bug: "297542292"
 }
@@ -20,3 +27,24 @@
     description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
     bug: "299069460"
 }
+
+flag {
+    name: "bugreport_mode_max_value"
+    namespace: "telephony"
+    description: "Introduce a constant as maximum value of bugreport mode."
+    bug: "305067125"
+}
+
+flag {
+    name: "adpf_prefer_power_efficiency"
+    namespace: "game"
+    description: "Guards the ADPF power efficiency API"
+    bug: "288117936"
+}
+
+flag {
+    name: "battery_saver_supported_check_api"
+    namespace: "backstage_power"
+    description: "Guards a new API in PowerManager to check if battery saver is supported or not."
+    bug: "305067031"
+}
\ No newline at end of file
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index dfc43f4..2d53341 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -16,6 +16,7 @@
 
 package android.os.health;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -24,7 +25,6 @@
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.IPowerStatsService;
 import android.os.PowerMonitor;
@@ -157,39 +157,15 @@
     }
 
     /**
-     * Returns a list of supported power monitors, which include raw ODPM rails and
-     * modeled energy consumers.  If ODPM is unsupported by PowerStats HAL, this method returns
-     * an empty array.
+     * Asynchronously retrieves a list of supported  {@link PowerMonitor}'s, which include raw ODPM
+     * (on-device power rail monitor) rails and modeled energy consumers.  If ODPM is unsupported
+     * on this device this method delivers an empty list.
      *
-     * @hide
-     */
-    @NonNull
-    public List<PowerMonitor> getSupportedPowerMonitors() {
-        synchronized (mPowerMonitorsLock) {
-            if (mPowerMonitorsInfo != null) {
-                return mPowerMonitorsInfo;
-            }
-        }
-        ConditionVariable lock = new ConditionVariable();
-        // Populate mPowerMonitorsInfo by side-effect
-        getSupportedPowerMonitors(null, unused -> lock.open());
-        lock.block();
-
-        synchronized (mPowerMonitorsLock) {
-            return mPowerMonitorsInfo;
-        }
-    }
-
-    /**
-     * Asynchronously retrieves a list of supported power monitors, see
-     * {@link #getSupportedPowerMonitors()}
-     *
-     * @param handler optional Handler to deliver the callback. If not supplied, the callback
-     *                may be invoked on an arbitrary thread.
+     * @param handler  optional Handler to deliver the callback. If not supplied, the callback
+     *                 may be invoked on an arbitrary thread.
      * @param onResult callback for the result
-     *
-     * @hide
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public void getSupportedPowerMonitors(@Nullable Handler handler,
             @NonNull Consumer<List<PowerMonitor>> onResult) {
         final List<PowerMonitor> result;
@@ -229,35 +205,6 @@
         }
     }
 
-    /**
-     * Retrieves the accumulated power consumption reported by the specified power monitors.
-     *
-     * @param powerMonitors power monitors to be returned.
-     *
-     * @hide
-     */
-    @NonNull
-    public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) {
-        PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1];
-        RuntimeException[] outException = new RuntimeException[1];
-        ConditionVariable lock = new ConditionVariable();
-        getPowerMonitorReadings(powerMonitors, null,
-                pms -> {
-                    outReadings[0] = pms;
-                    lock.open();
-                },
-                error -> {
-                    outException[0] = error;
-                    lock.open();
-                }
-        );
-        lock.block();
-        if (outException[0] != null) {
-            throw outException[0];
-        }
-        return outReadings[0];
-    }
-
     private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
             Comparator.comparingInt(pm -> pm.index);
 
@@ -270,9 +217,8 @@
      *                      may be invoked on an arbitrary thread.
      * @param onSuccess     callback for the result
      * @param onError       callback invoked in case of an error
-     *
-     * @hide
      */
+    @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) {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 369a193..3ecf74e 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,20 +134,20 @@
     @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void setDebugFlags(int flags, int mask) = 60;
     @EnforcePermission("STORAGE_INTERNAL")
-    void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61;
+    void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61;
     @EnforcePermission("STORAGE_INTERNAL")
-    void destroyUserKey(int userId) = 62;
+    void destroyUserStorageKeys(int userId) = 62;
     @EnforcePermission("STORAGE_INTERNAL")
-    void unlockUserKey(int userId, int serialNumber, in byte[] secret) = 63;
+    void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63;
     @EnforcePermission("STORAGE_INTERNAL")
-    void lockUserKey(int userId) = 64;
-    boolean isUserKeyUnlocked(int userId) = 65;
+    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;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
     @EnforcePermission("STORAGE_INTERNAL")
-    void setUserKeyProtection(int userId, in byte[] secret) = 70;
+    void setCeStorageProtection(int userId, in byte[] secret) = 70;
     @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS")
     void fstrim(int flags, IVoldTaskListener listener) = 72;
     AppFuseMount mountProxyFileDescriptorBridge() = 73;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index ee387e7..2d1802a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1589,28 +1589,64 @@
                 DEFAULT_FULL_THRESHOLD_BYTES);
     }
 
-    /** {@hide} */
-    public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+    /**
+     * Creates the keys for a user's credential-encrypted (CE) and device-encrypted (DE) storage.
+     * <p>
+     * This creates the user's CE key and DE key for internal storage, then adds them to the kernel.
+     * Then, if the user is not ephemeral, this stores the DE key (encrypted) on flash.  (The CE key
+     * is not stored until {@link IStorageManager#setCeStorageProtection()}.)
+     * <p>
+     * This does not create the CE and DE directories themselves.  For that, see {@link
+     * #prepareUserStorage()}.
+     * <p>
+     * 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) {
         try {
-            mStorageManager.createUserKey(userId, serialNumber, ephemeral);
+            mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /** {@hide} */
-    public void destroyUserKey(int userId) {
+    /**
+     * Destroys the keys for a user's credential-encrypted (CE) and device-encrypted (DE) storage.
+     * <p>
+     * This evicts the keys from the kernel (if present), which "locks" the corresponding
+     * directories.  Then, this deletes the encrypted keys from flash.  This operates on all the
+     * user's CE and DE keys, for both internal and adoptable storage.
+     * <p>
+     * This does not destroy the CE and DE directories themselves.  For that, see {@link
+     * #destroyUserStorage()}.
+     * <p>
+     * This is only intended to be called by UserManagerService, as part of removing a user.
+     *
+     * @param userId ID of the user
+     * @throws RuntimeException on error.  On error, as many things as possible are still destroyed.
+     * @hide
+     */
+    public void destroyUserStorageKeys(int userId) {
         try {
-            mStorageManager.destroyUserKey(userId);
+            mStorageManager.destroyUserStorageKeys(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /** {@hide} */
-    public void lockUserKey(int userId) {
+    /**
+     * Locks the user's credential-encrypted (CE) storage.
+     *
+     * @hide
+     */
+    public void lockCeStorage(int userId) {
         try {
-            mStorageManager.lockUserKey(userId);
+            mStorageManager.lockCeStorage(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1637,17 +1673,26 @@
     /** {@hide} */
     @TestApi
     public static boolean isUserKeyUnlocked(int userId) {
+        return isCeStorageUnlocked(userId);
+    }
+
+    /**
+     * Returns true if the user's credential-encrypted (CE) storage is unlocked.
+     *
+     * @hide
+     */
+    public static boolean isCeStorageUnlocked(int userId) {
         if (sStorageManager == null) {
             sStorageManager = IStorageManager.Stub
                     .asInterface(ServiceManager.getService("mount"));
         }
         if (sStorageManager == null) {
-            Slog.w(TAG, "Early during boot, assuming locked");
+            Slog.w(TAG, "Early during boot, assuming CE storage is locked");
             return false;
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            return sStorageManager.isUserKeyUnlocked(userId);
+            return sStorageManager.isCeStorageUnlocked(userId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         } finally {
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index bde334a..92e4967 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -65,6 +65,8 @@
     @VibrationIntensity
     private final int mDefaultRingVibrationIntensity;
 
+    private final boolean mDefaultKeyboardVibrationEnabled;
+
     /** @hide */
     public VibrationConfig(@Nullable Resources resources) {
         mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -76,6 +78,8 @@
 
         mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
                 com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
+        mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
+                com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
 
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -157,6 +161,14 @@
         return mIgnoreVibrationsOnWirelessCharger;
     }
 
+    /**
+     * Whether keyboard vibration settings is enabled by default.
+     * @hide
+     */
+    public boolean isDefaultKeyboardVibrationEnabled() {
+        return mDefaultKeyboardVibrationEnabled;
+    }
+
     /** Get the default vibration intensity for given usage. */
     @VibrationIntensity
     public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 88f62f3..69d86a6 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -27,3 +27,20 @@
     description: "Enables the APIs for vibration serialization/deserialization."
     bug: "245129509"
 }
+
+flag {
+    namespace: "haptics"
+    name: "haptic_feedback_vibration_oem_customization_enabled"
+    description: "Enables OEMs/devices to customize vibrations for haptic feedback"
+    # Make read only. This is because the flag is used only once, and this could happen before
+    # the read-write flag values propagate to the device.
+    is_fixed_read_only: true
+    bug: "291128479"
+}
+
+flag {
+    namespace: "haptics"
+    name: "keyboard_category_enabled"
+    description: "Enables the independent keyboard vibration settings feature"
+    bug: "289107579"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d8534dd..0798f65 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -21,3 +21,24 @@
     description: "enable role controller in system server"
     bug: "302562590"
 }
+
+flag {
+  name: "set_next_attribution_source"
+  namespace: "permissions"
+  description: "enable AttributionSource.setNextAttributionSource"
+  bug: "304478648"
+}
+
+flag {
+    name: "should_register_attribution_source"
+    namespace: "permissions"
+    description: "enable the shouldRegisterAttributionSource API"
+    bug: "305057691"
+}
+
+flag {
+  name: "attribution_source_constructor"
+  namespace: "permissions"
+  description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)"
+  bug: "304478648"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5c64389..a2f1ce1e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1800,7 +1800,6 @@
      * Input: Nothing.
      * <p>
      * Output: Nothing.
-     * @see android.service.notification.NotificationAssistantService
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS =
@@ -2507,8 +2506,8 @@
      * to the caller package.
      * <p>
      * <b>NOTE: </b> Applications should call
-     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()}
-     * and only use this action to start an activity if they return {@code false}.
+     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService(
+     * ComponentName)} and only use this action to start an activity if they return {@code false}.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CREDENTIAL_PROVIDER =
@@ -5130,6 +5129,14 @@
                 "hardware_haptic_feedback_intensity";
 
         /**
+         * Whether keyboard vibration feedback is enabled. The value is boolean (1 or 0).
+         *
+         * @hide
+         */
+        @Readable
+        public static final String KEYBOARD_VIBRATION_ENABLED = "keyboard_vibration_enabled";
+
+        /**
          * Ringer volume. This is used internally, changing this value will not
          * change the volume. See AudioManager.
          *
@@ -5354,6 +5361,37 @@
         public static final Uri NOTIFICATION_SOUND_CACHE_URI = getUriFor(NOTIFICATION_SOUND_CACHE);
 
         /**
+         * When enabled, notifications attention effects: sound, vibration, flashing
+         * will have a cooldown timer.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String NOTIFICATION_COOLDOWN_ENABLED =
+            "notification_cooldown_enabled";
+
+        /**
+         * When enabled, notification cooldown will apply to all notifications.
+         * Otherwise cooldown will only apply to conversations.
+         *
+         * The value 1 - enable, 0 - disable
+         * Only valid if {@code NOTIFICATION_COOLDOWN_ENABLED} is enabled.
+         * @hide
+         */
+        public static final String NOTIFICATION_COOLDOWN_ALL =
+            "notification_cooldown_all";
+
+        /**
+         * When enabled, notification attention effects will be restricted to vibration only
+         * as long as the screen is unlocked.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED =
+            "notification_cooldown_vibrate_unlocked";
+
+        /**
          * Persistent store for the system-wide default alarm alert.
          *
          * @see #RINGTONE
@@ -11180,6 +11218,12 @@
         public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
 
         /**
+         * Volume dialog timeout in ms.
+         * @hide
+         */
+        public static final String VOLUME_DIALOG_DISMISS_TIMEOUT = "volume_dialog_dismiss_timeout";
+
+        /**
          * What behavior should be invoked when the volume hush gesture is triggered
          * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE.
          *
@@ -11609,6 +11653,15 @@
                 "accessibility_magnification_joystick_enabled";
 
         /**
+         * Setting that specifies whether the display magnification is enabled via a system-wide
+         * two fingers triple tap gesture.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED =
+                "accessibility_magnification_two_finger_triple_tap_enabled";
+
+        /**
          * Controls magnification enable gesture. Accessibility magnification can have one or more
          * enable gestures.
          *
@@ -12014,6 +12067,13 @@
         public static final String DND_CONFIGS_MIGRATED = "dnd_settings_migrated";
 
         /**
+         * Controls whether to hide private space entry point in All Apps
+         *
+         * @hide
+         */
+        public static final String HIDE_PRIVATESPACE_ENTRY_POINT = "hide_privatespace_entry_point";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
@@ -12338,7 +12398,7 @@
          * Value to specify if the device's UTC system clock should be set automatically, e.g. using
          * telephony signals like NITZ, or other sources like GNSS or NTP.
          *
-         * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+         * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
          * automatic time detection instead of directly observing this setting as it may be ignored
          * by the time_detector service under various conditions.
          *
@@ -12351,7 +12411,7 @@
          * Value to specify if the device's time zone system property should be set automatically,
          * e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location.
          *
-         * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+         * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
          * automatic time zone detection instead of directly observing this setting as it may be
          * ignored by the time_zone_detector service under various conditions.
          *
@@ -12465,6 +12525,13 @@
                 "wireless_charging_started_sound";
 
         /**
+         * Whether to auto enable reverse charging once plugged-in.
+         * @hide
+         */
+        public static final String REVERSE_CHARGING_AUTO_ON =
+                "settings_key_reverse_charging_auto_turn_on";
+
+        /**
          * URI for "wired charging started" sound.
          * @hide
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a391571..27ad45d 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3196,6 +3196,16 @@
         public static final String ALWAYS_ON = "always_on";
 
         /**
+         * The infrastructure bitmask which the APN can be used on. For example, some APNs can only
+         * be used when the device is on cellular, on satellite, or both. The default value is
+         * 1 (INFRASTRUCTURE_CELLULAR).
+         *
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String INFRASTRUCTURE_BITMASK = "infrastructure_bitmask";
+
+        /**
          * MVNO type:
          * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
          * <P>Type: TEXT</P>
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 800149c..94eca3d 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -12,6 +12,7 @@
     namespace: "hardware_backed_security"
     description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
     bug: "296464083"
+    is_fixed_read_only: true
 }
 
 flag {
diff --git a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl b/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl
deleted file mode 100644
index dbffd5f..0000000
--- a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl
+++ /dev/null
@@ -1,32 +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 android.security.keymaster;
-
-import android.security.keymaster.KeyAttestationApplicationId;
-import android.security.keymaster.KeyAttestationPackageInfo;
-import android.content.pm.Signature;
-
-/**
- * This must be kept manually in sync with system/security/keystore until AIDL
- * can generate both Java and C++ bindings.
- *
- * @hide
- */
-interface IKeyAttestationApplicationIdProvider {
-    /* keep in sync with /system/security/keystore/keystore_attestation_id.cpp */
-    KeyAttestationApplicationId getKeyAttestationApplicationId(int uid);
-}
diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl b/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl
deleted file mode 100644
index 9f6ff58..0000000
--- a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl
+++ /dev/null
@@ -1,22 +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 android.security.keymaster;
-
-/* The cpp_header is relative to system/security/keystore/include
- * Link against libkeystore_binder to make use of the native implementation of this Parcelable.
- */
-parcelable KeyAttestationApplicationId cpp_header "keystore/KeyAttestationApplicationId.h";
diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.java b/core/java/android/security/keymaster/KeyAttestationApplicationId.java
deleted file mode 100644
index 670f30e1b..0000000
--- a/core/java/android/security/keymaster/KeyAttestationApplicationId.java
+++ /dev/null
@@ -1,74 +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 android.security.keymaster;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * @hide
- * The information aggregated by this class is used by keystore to identify a caller of the
- * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore
- * can only determine a caller by uid granularity, and a uid can be shared by multiple packages.
- * The remote party must decide if it trusts all of the packages enough to consider the
- * confidentiality of the key material in question intact.
- */
-public class KeyAttestationApplicationId implements Parcelable {
-    private final KeyAttestationPackageInfo[] mAttestationPackageInfos;
-
-    /**
-     * @param mAttestationPackageInfos
-     */
-    public KeyAttestationApplicationId(KeyAttestationPackageInfo[] mAttestationPackageInfos) {
-        super();
-        this.mAttestationPackageInfos = mAttestationPackageInfos;
-    }
-
-    /**
-     * @return the mAttestationPackageInfos
-     */
-    public KeyAttestationPackageInfo[] getAttestationPackageInfos() {
-        return mAttestationPackageInfos;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeTypedArray(mAttestationPackageInfos, flags);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationApplicationId> CREATOR
-            = new Parcelable.Creator<KeyAttestationApplicationId>() {
-        @Override
-        public KeyAttestationApplicationId createFromParcel(Parcel source) {
-            return new KeyAttestationApplicationId(source);
-        }
-
-        @Override
-        public KeyAttestationApplicationId[] newArray(int size) {
-            return new KeyAttestationApplicationId[size];
-        }
-    };
-
-    KeyAttestationApplicationId(Parcel source) {
-        mAttestationPackageInfos = source.createTypedArray(KeyAttestationPackageInfo.CREATOR);
-    }
-}
diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl b/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl
deleted file mode 100644
index f8b843b..0000000
--- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl
+++ /dev/null
@@ -1,22 +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 android.security.keymaster;
-
-/* The cpp_header is relative to system/security/keystore/include
- * Link against libkeystore_binder to make use of the native implementation of this Parcelable.
- */
-parcelable KeyAttestationPackageInfo cpp_header "keystore/KeyAttestationPackageInfo.h";
diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java b/core/java/android/security/keymaster/KeyAttestationPackageInfo.java
deleted file mode 100644
index c0b8d8d..0000000
--- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java
+++ /dev/null
@@ -1,95 +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 android.security.keymaster;
-
-import android.content.pm.Signature;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * @hide
- * This class constitutes and excerpt from the PackageManager's PackageInfo for the purpose of
- * key attestation. It is part of the KeyAttestationApplicationId, which is used by
- * keystore to identify the caller of the keystore API towards a remote party.
- */
-public class KeyAttestationPackageInfo implements Parcelable {
-    private final String mPackageName;
-    private final long mPackageVersionCode;
-    private final Signature[] mPackageSignatures;
-
-    /**
-     * @param mPackageName
-     * @param mPackageVersionCode
-     * @param mPackageSignatures
-     */
-    public KeyAttestationPackageInfo(
-            String mPackageName, long mPackageVersionCode, Signature[] mPackageSignatures) {
-        super();
-        this.mPackageName = mPackageName;
-        this.mPackageVersionCode = mPackageVersionCode;
-        this.mPackageSignatures = mPackageSignatures;
-    }
-    /**
-     * @return the mPackageName
-     */
-    public String getPackageName() {
-        return mPackageName;
-    }
-    /**
-     * @return the mPackageVersionCode
-     */
-    public long getPackageVersionCode() {
-        return mPackageVersionCode;
-    }
-    /**
-     * @return the mPackageSignatures
-     */
-    public Signature[] getPackageSignatures() {
-        return mPackageSignatures;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mPackageName);
-        dest.writeLong(mPackageVersionCode);
-        dest.writeTypedArray(mPackageSignatures, flags);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationPackageInfo> CREATOR
-            = new Parcelable.Creator<KeyAttestationPackageInfo>() {
-        @Override
-        public KeyAttestationPackageInfo createFromParcel(Parcel source) {
-            return new KeyAttestationPackageInfo(source);
-        }
-
-        @Override
-        public KeyAttestationPackageInfo[] newArray(int size) {
-            return new KeyAttestationPackageInfo[size];
-        }
-    };
-
-    private KeyAttestationPackageInfo(Parcel source) {
-        mPackageName = source.readString();
-        mPackageVersionCode = source.readLong();
-        mPackageSignatures = source.createTypedArray(Signature.CREATOR);
-    }
-}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index a29bf7a..1afe8d9 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -1401,6 +1401,7 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeString(mId);
         parcel.writeInt(mEligibleReason);
+        parcel.writeTypedObject(mAuthenticationExtras, flags);
     }
 
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1436,6 +1437,7 @@
                     android.content.IntentSender.class);
             final String datasetId = parcel.readString();
             final int eligibleReason = parcel.readInt();
+            final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR);
 
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
@@ -1480,6 +1482,7 @@
                         fieldDialogPresentation);
             }
             builder.setAuthentication(authentication);
+            builder.setAuthenticationExtras(authenticationExtras);
             builder.setId(datasetId);
             Dataset dataset = builder.build();
             dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 8cf2ce4..7ec1483 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -97,8 +97,6 @@
      */
     public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
 
-    // The flag value 0x20 has been defined in AutofillManager.
-
     /**
      * Indicates the request supports fill dialog presentation for the fields, the
      * system will send the request when the activity just started.
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 3fb94ee..7ea74d3 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -406,7 +406,7 @@
          *
          * Call this when a field has been detected with a type.
          *
-         * Altough similiarly named with {@link setFieldClassificationIds},
+         * Altough similiarly named with {@link #setFieldClassificationIds},
          * it provides a different functionality - setFieldClassificationIds should
          * be used when a field is only suspected to be Autofillable.
          * This method should be used when a field is certainly Autofillable
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 954cc96..53706cd3 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -813,7 +813,7 @@
          * If no {@link #Builder(int, AutofillId[]) required ids},
          * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
          * were set, Save Dialog will only be triggered if platform detection is enabled, which
-         * is indicated when {@link FillRequest.getHints()} is not empty.
+         * is indicated when {@link FillRequest#getHints()} is not empty.
          */
         public SaveInfo build() {
             throwIfDestroyed();
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index df93433..5d96f69 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -143,7 +143,7 @@
          *
          * <p> Note that as a provider service you will only be able to set a remote entry if :
          * - Provider service possesses the
-         * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+         * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
          * - Provider service is configured as the provider that can provide remote entries.
          *
          * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse}
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index 5ed06ac..ae6ca25 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -160,7 +160,7 @@
          *
          * <p> Note that as a provider service you will only be able to set a remote entry if :
          * - Provider service possesses the
-         * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+         * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
          * - Provider service is configured as the provider that can provide remote entries.
          *
          * If the above conditions are not met, setting back {@link BeginGetCredentialResponse}
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index e755581..c3524c5 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -103,7 +103,7 @@
      * of other applications.
      *
      * Android system makes sure that only applications that poses the permission
-     * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+     * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
      * the incoming {@link android.credentials.GetCredentialRequest} or
      * {@link android.credentials.CreateCredentialRequest}.
      */
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 0aa6a23..514d722 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -45,8 +45,8 @@
 import android.util.Slog;
 import android.util.Xml;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -135,8 +135,8 @@
     }
 
     /**
-     * Constructs an information instance of the credential provider for testing purposes. Does
-     * not run any verifications and passes parameters as is.
+     * Constructs an information instance of the credential provider for testing purposes. Does not
+     * run any verifications and passes parameters as is.
      */
     @VisibleForTesting
     public static CredentialProviderInfo createForTests(
@@ -151,7 +151,6 @@
                 .setSystemProvider(isSystemProvider)
                 .addCapabilities(capabilities)
                 .build();
-
     }
 
     private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -267,15 +266,21 @@
                                     allAttributes,
                                     com.android.internal.R.styleable.CredentialProvider);
                     builder.setSettingsSubtitle(
-                            afsAttributes.getString(
+                            getAfsAttributeSafe(
+                                    afsAttributes,
                                     R.styleable.CredentialProvider_settingsSubtitle));
+                    builder.setSettingsActivity(
+                            getAfsAttributeSafe(
+                                    afsAttributes,
+                                    R.styleable.CredentialProvider_settingsActivity));
                 } catch (Exception e) {
-                    Slog.e(TAG, "Failed to get XML attr", e);
+                    Slog.w(TAG, "Failed to get XML attr for metadata", e);
                 } finally {
                     if (afsAttributes != null) {
                         afsAttributes.recycle();
                     }
                 }
+
                 builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
             } else {
                 Slog.w(TAG, "Meta-data does not start with credential-provider-service tag");
@@ -287,6 +292,21 @@
         return builder;
     }
 
+    private static @Nullable String getAfsAttributeSafe(
+            @Nullable TypedArray afsAttributes, int resId) {
+        if (afsAttributes == null) {
+            return null;
+        }
+
+        try {
+            return afsAttributes.getString(resId);
+        } catch (Exception e) {
+            Slog.w(TAG, "Failed to get XML attr from afs attributes", e);
+        }
+
+        return null;
+    }
+
     private static List<String> parseXmlProviderOuterCapabilities(
             XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
         final List<String> capabilities = new ArrayList<>();
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 07f8aac..759953e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -896,8 +896,7 @@
      * <p>This method will throw a security exception if you don't have access to notifications
      * for the given user.</p>
      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
-     * device} or be the {@link NotificationAssistantService notification assistant} in order to
-     * use this method.
+     * device} or be the notification assistant in order to use this method.
      *
      * @param pkg The package to retrieve channels for.
      */
@@ -920,8 +919,7 @@
      * <p>This method will throw a security exception if you don't have access to notifications
      * for the given user.</p>
      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
-     * device} or be the {@link NotificationAssistantService notification assistant} in order to
-     * use this method.
+     * device} or be the notification assistant in order to use this method.
      *
      * @param pkg The package to retrieve channel groups for.
      */
@@ -2001,15 +1999,28 @@
 
         /**
          * Returns a list of smart {@link Notification.Action} that can be added by the
-         * {@link NotificationAssistantService}
+         * notification assistant.
          */
         public @NonNull List<Notification.Action> getSmartActions() {
             return mSmartActions == null ? Collections.emptyList() : mSmartActions;
         }
 
+
         /**
-         * Returns a list of smart replies that can be added by the
-         * {@link NotificationAssistantService}
+         * Sets the smart {@link Notification.Action} objects.
+         *
+         * Should ONLY be used in cases where smartActions need to be removed from, then restored
+         * on, Ranking objects during Parceling, when they are transmitted between processes via
+         * Shared Memory.
+         *
+         * @hide
+         */
+        public void setSmartActions(@Nullable ArrayList<Notification.Action> smartActions) {
+            mSmartActions = smartActions;
+        }
+
+        /**
+         * Returns a list of smart replies that can be added by the notification assistant.
          */
         public @NonNull List<CharSequence> getSmartReplies() {
             return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
@@ -2353,11 +2364,9 @@
 
         /**
          * Get a reference to the actual Ranking object corresponding to the key.
-         * Used only by unit tests.
          *
          * @hide
          */
-        @VisibleForTesting
         public Ranking getRawRankingObject(String key) {
             return mRankings.get(key);
         }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index f3b4c6d..2a4cbaf 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -17,7 +17,8 @@
 
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.app.Notification;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SharedMemory;
@@ -25,17 +26,18 @@
 import android.system.OsConstants;
 
 import androidx.annotation.NonNull;
-
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import androidx.annotation.VisibleForTesting;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents an update to notification rankings.
+ *
  * @hide
  */
 @SuppressLint({"ParcelNotFinal", "ParcelCreator"})
-@TestApi
 public class NotificationRankingUpdate implements Parcelable {
     private final NotificationListenerService.RankingMap mRankingMap;
 
@@ -55,8 +57,7 @@
      * @hide
      */
     public NotificationRankingUpdate(Parcel in) {
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             // Recover the ranking map from the SharedMemory and store it in mapParcel.
             final Parcel mapParcel = Parcel.obtain();
             ByteBuffer buffer = null;
@@ -64,6 +65,7 @@
                 // The ranking map should be stored in shared memory when it is parceled, so we
                 // unwrap the SharedMemory object.
                 mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class);
+                Bundle smartActionsBundle = in.readBundle(getClass().getClassLoader());
 
                 // In the case that the ranking map can't be read, readParcelable may return null.
                 // In this case, we set mRankingMap to null;
@@ -82,15 +84,20 @@
                 mapParcel.unmarshall(payload, 0, payload.length);
                 mapParcel.setDataPosition(0);
 
-                mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(),
-                        android.service.notification.NotificationListenerService.RankingMap.class);
+                mRankingMap =
+                        mapParcel.readParcelable(
+                                getClass().getClassLoader(),
+                                NotificationListenerService.RankingMap.class);
+
+                addSmartActionsFromBundleToRankingMap(smartActionsBundle);
+
             } catch (ErrnoException e) {
                 // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
                 // avoid crashes; change to Log.wtf.
                 throw new RuntimeException(e);
             } finally {
                 mapParcel.recycle();
-                if (buffer != null) {
+                if (buffer != null && mRankingMapFd != null) {
                     mRankingMapFd.unmap(buffer);
                     mRankingMapFd.close();
                 }
@@ -102,10 +109,34 @@
     }
 
     /**
-     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
+     * For each key in the rankingMap, extracts lists of smart actions stored in the provided
+     * bundle and adds them to the corresponding Ranking object in the provided ranking
+     * map, then returns the rankingMap.
+     *
      * @hide
      */
-    @TestApi
+    private void addSmartActionsFromBundleToRankingMap(Bundle smartActionsBundle) {
+        if (smartActionsBundle == null) {
+            return;
+        }
+
+        String[] rankingMapKeys = mRankingMap.getOrderedKeys();
+        for (int i = 0; i < rankingMapKeys.length; i++) {
+            String key = rankingMapKeys[i];
+            ArrayList<Notification.Action> smartActions =
+                    smartActionsBundle.getParcelableArrayList(key, Notification.Action.class);
+            // Get the ranking object from the ranking map.
+            NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key);
+            ranking.setSmartActions(smartActions);
+        }
+    }
+
+    /**
+     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
+     *
+     * @hide
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     public final boolean isFdNotNullAndClosed() {
         return mRankingMapFd != null && mRankingMapFd.getFd() == -1;
     }
@@ -142,12 +173,47 @@
      */
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             final Parcel mapParcel = Parcel.obtain();
+            ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
+            Bundle smartActionsBundle = new Bundle();
+
+            // We need to separate the SmartActions from the RankingUpdate objects.
+            // SmartActions can contain PendingIntents, which cannot be marshalled,
+            // so we extract them to send separately.
+            String[] rankingMapKeys = mRankingMap.getOrderedKeys();
+            for (int i = 0; i < rankingMapKeys.length; i++) {
+                String key = rankingMapKeys[i];
+                NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key);
+
+                // Removes the SmartActions and stores them in a separate map.
+                // Note that getSmartActions returns a Collections.emptyList() if there are no
+                // smart actions, and we don't want to needlessly store an empty list object, so we
+                // check for null before storing.
+                List<Notification.Action> smartActions = ranking.getSmartActions();
+                if (!smartActions.isEmpty()) {
+                    smartActionsBundle.putParcelableList(key, smartActions);
+                }
+
+                // Create a copy of the ranking object that doesn't have the smart actions.
+                NotificationListenerService.Ranking rankingCopy =
+                        new NotificationListenerService.Ranking();
+                rankingCopy.populate(ranking);
+                rankingCopy.setSmartActions(null);
+                marshalableRankings.add(rankingCopy);
+            }
+
+            // Create a new marshalable RankingMap.
+            NotificationListenerService.RankingMap marshalableRankingMap =
+                    new NotificationListenerService.RankingMap(
+                            marshalableRankings.toArray(
+                                    new NotificationListenerService.Ranking[0]
+                            )
+                    );
+
             try {
                 // Parcels the ranking map and measures its size.
-                mapParcel.writeParcelable(mRankingMap, flags);
+                mapParcel.writeParcelable(marshalableRankingMap, flags);
                 int mapSize = mapParcel.dataSize();
 
                 // Creates a new SharedMemory object with enough space to hold the ranking map.
@@ -158,15 +224,14 @@
 
                 // Gets a read/write buffer mapping the entire shared memory region.
                 final ByteBuffer buffer = mRankingMapFd.mapReadWrite();
-
                 // Puts the ranking map into the shared memory region buffer.
                 buffer.put(mapParcel.marshall(), 0, mapSize);
-
                 // Protects the region from being written to, by setting it to be read-only.
                 mRankingMapFd.setProtect(OsConstants.PROT_READ);
-
                 // Puts the SharedMemory object in the parcel.
                 out.writeParcelable(mRankingMapFd, flags);
+                // Writes the Parceled smartActions separately.
+                out.writeBundle(smartActionsBundle);
             } catch (ErrnoException e) {
                 // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
                 // avoid crashes; change to Log.wtf.
@@ -180,8 +245,8 @@
     }
 
     /**
-    * @hide
-    */
+     * @hide
+     */
     public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR
             = new Parcelable.Creator<NotificationRankingUpdate>() {
         public NotificationRankingUpdate createFromParcel(Parcel parcel) {
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
new file mode 100644
index 0000000..52d4d47
--- /dev/null
+++ b/core/java/android/service/notification/flags.aconfig
@@ -0,0 +1,17 @@
+package: "android.service.notification"
+
+flag {
+  name: "ranking_update_ashmem"
+  namespace: "systemui"
+  description: "This flag controls moving ranking update contents into ashmem"
+  bug: "284297289"
+}
+
+flag {
+  name: "notification_lifetime_extension_refactor"
+    namespace: "systemui"
+    description: "Enables moving notification lifetime extension management from SystemUI to "
+        "Notification Manager Service"
+    bug: "299448097"
+}
+
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index 4a4fd04..bf129c5 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -372,9 +372,9 @@
         }
 
         /**
-         * Set of locations this card might be useful at. If {@link
-         * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
-         * shown to the user when a user is near one of these locations.
+         * Set of locations this card might be useful at. If
+         * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is
+         * enabled, the card might be shown to the user when a user is near one of these locations.
          */
         @NonNull
         public Builder setCardLocations(@NonNull List<Location> cardLocations) {
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index 5b57fc7..dce874d 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -1,9 +1,7 @@
 # Bug component: 814982
 
 asalo@google.com
-augale@google.com
 eejiang@google.com
 payamp@google.com
 siddikap@google.com
-svetoslavganov@google.com
 tgadh@google.com
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 7af7fe6..dfb1361 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -199,8 +199,12 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+        Consumer<AbstractDetector> onDestroyListener;
         synchronized (mLock) {
-            mOnDestroyListener.accept(this);
+            onDestroyListener = mOnDestroyListener;
+        }
+        if (onDestroyListener != null) {
+            onDestroyListener.accept(this);
         }
     }
 
@@ -259,5 +263,12 @@
                         result != null ? result : new HotwordRejectedResult.Builder().build());
             }));
         }
+
+        @Override
+        public void onTrainingData(HotwordTrainingData data) {
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
+                mCallback.onTrainingData(data);
+            }));
+        }
     }
 }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 6a82f6d..875031f 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -306,6 +306,7 @@
     private static final int MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE = 9;
     private static final int MSG_DETECTION_SOUND_TRIGGER_FAILURE = 10;
     private static final int MSG_DETECTION_UNKNOWN_FAILURE = 11;
+    private static final int MSG_HOTWORD_TRAINING_DATA = 12;
 
     private final String mText;
     private final Locale mLocale;
@@ -1653,6 +1654,16 @@
         }
 
         @Override
+        public void onTrainingData(@NonNull HotwordTrainingData data) {
+            if (DBG) {
+                Slog.d(TAG, "onTrainingData(" + data + ")");
+            } else {
+                Slog.i(TAG, "onTrainingData");
+            }
+            Message.obtain(mHandler, MSG_HOTWORD_TRAINING_DATA, data).sendToTarget();
+        }
+
+        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
             Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
@@ -1783,6 +1794,9 @@
                     case MSG_DETECTION_UNKNOWN_FAILURE:
                         mExternalCallback.onUnknownFailure((String) message.obj);
                         break;
+                    case MSG_HOTWORD_TRAINING_DATA:
+                        mExternalCallback.onTrainingData((HotwordTrainingData) message.obj);
+                        break;
                     default:
                         super.handleMessage(message);
                 }
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index ccf8b67..13b6a9a 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -19,6 +19,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -39,6 +40,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
+import android.service.voice.flags.Flags;
 import android.speech.IRecognitionServiceManager;
 import android.util.Log;
 import android.view.contentcapture.ContentCaptureManager;
@@ -443,5 +445,30 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * Informs the {@link HotwordDetector} when there is training data.
+         *
+         * <p> A daily limit of 20 is enforced on training data events sent. Number events egressed
+         * are tracked across UTC day (24-hour window) and count is reset at midnight
+         * (UTC 00:00:00). To be informed of failures to egress training data due to limit being
+         * reached, the associated hotword detector should listen for
+         * {@link HotwordDetectionServiceFailure#ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED}
+         * events in {@link HotwordDetector.Callback#onFailure(HotwordDetectionServiceFailure)}.
+         *
+         * @param data Training data determined by the service. This is provided to the
+         *               {@link HotwordDetector}.
+         */
+        @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+        public void onTrainingData(@NonNull HotwordTrainingData data) {
+            requireNonNull(data);
+            try {
+                Log.d(TAG, "onTrainingData");
+                mRemoteCallback.onTrainingData(data);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
     }
 }
diff --git a/core/java/android/service/voice/HotwordDetectionServiceFailure.java b/core/java/android/service/voice/HotwordDetectionServiceFailure.java
index 5cf245d..420dac1 100644
--- a/core/java/android/service/voice/HotwordDetectionServiceFailure.java
+++ b/core/java/android/service/voice/HotwordDetectionServiceFailure.java
@@ -16,12 +16,14 @@
 
 package android.service.voice;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.service.voice.flags.Flags;
 import android.text.TextUtils;
 
 import java.lang.annotation.Retention;
@@ -79,6 +81,14 @@
      */
     public static final int ERROR_CODE_REMOTE_EXCEPTION = 7;
 
+    /** Indicates failure to egress training data due to limit being exceeded. */
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+    public static final int ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED = 8;
+
+    /** Indicates failure to egress training data due to security exception. */
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+    public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9;
+
     /**
      * @hide
      */
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 32a93ee..16a6dbe 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -27,6 +28,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.SharedMemory;
+import android.service.voice.flags.Flags;
 
 import java.io.PrintWriter;
 
@@ -244,6 +246,19 @@
         void onRejected(@NonNull HotwordRejectedResult result);
 
         /**
+         * Called by the {@link HotwordDetectionService} to egress training data to the
+         * {@link HotwordDetector}. This data can be used for improving and analyzing hotword
+         * detection models.
+         *
+         * @param data Training data to be egressed provided by the
+         *               {@link HotwordDetectionService}.
+         */
+        @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+        default void onTrainingData(@NonNull HotwordTrainingData data) {
+            return;
+        }
+
+        /**
          * Called when the {@link HotwordDetectionService} or {@link VisualQueryDetectionService} is
          * created by the system and given a short amount of time to report their initialization
          * state.
diff --git a/core/java/android/service/voice/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java
index 91e34dc..916fa36b 100644
--- a/core/java/android/service/voice/HotwordTrainingAudio.java
+++ b/core/java/android/service/voice/HotwordTrainingAudio.java
@@ -16,6 +16,7 @@
 
 package android.service.voice;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -23,6 +24,7 @@
 import android.media.AudioFormat;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.service.voice.flags.Flags;
 
 import com.android.internal.util.DataClass;
 
@@ -33,6 +35,7 @@
  *
  * @hide
  */
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
 @DataClass(
         genConstructor = false,
         genBuilder = true,
@@ -65,12 +68,12 @@
 
     /**
      * App-defined identifier to distinguish hotword training audio instances.
-     */
+     * <p> Returns -1 if unset. */
     @NonNull
     private final int mAudioType;
 
     private static int defaultAudioType() {
-        return 0;
+        return -1;
     }
 
     /**
@@ -152,6 +155,7 @@
 
     /**
      * App-defined identifier to distinguish hotword training audio instances.
+     * <p> Returns -1 if unset.
      */
     @DataClass.Generated.Member
     public @NonNull int getAudioType() {
@@ -274,6 +278,7 @@
     /**
      * A builder for {@link HotwordTrainingAudio}
      */
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
     @SuppressWarnings("WeakerAccess")
     @DataClass.Generated.Member
     public static final class Builder extends BaseBuilder {
@@ -318,6 +323,7 @@
 
         /**
          * App-defined identifier to distinguish hotword training audio instances.
+         * <p> Returns -1 if unset.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setAudioType(@NonNull int value) {
@@ -368,7 +374,7 @@
     }
 
     @DataClass.Generated(
-            time = 1694193905346L,
+            time = 1697827049629L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java",
             inputSignatures = "public static final  int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate  int mHotwordOffsetMillis\nprivate  java.lang.String hotwordAudioToString()\nprivate static  int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java
index 31aeb9c..aa6dab3 100644
--- a/core/java/android/service/voice/HotwordTrainingData.java
+++ b/core/java/android/service/voice/HotwordTrainingData.java
@@ -16,10 +16,12 @@
 
 package android.service.voice;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.service.voice.flags.Flags;
 import android.text.TextUtils;
 
 import com.android.internal.util.DataClass;
@@ -47,6 +49,7 @@
         genParcelable = true,
         genToString = true)
 @SystemApi
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
 public final class HotwordTrainingData implements Parcelable {
     /** Max size for hotword training data in bytes. */
     public static int getMaxTrainingDataBytes() {
@@ -63,11 +66,11 @@
     }
 
     /** App-defined stage when hotword model timed-out while running.
-     * <p> Returns 0 if unset. */
+     * <p> Returns -1 if unset. */
     private final int mTimeoutStage;
 
     private static int defaultTimeoutStage() {
-        return 0;
+        return -1;
     }
 
     private void onConstructed() {
@@ -120,7 +123,7 @@
 
     /**
      * App-defined stage when hotword model timed-out while running.
-     * <p> Returns 0 if unset.
+     * <p> Returns -1 if unset.
      */
     @DataClass.Generated.Member
     public int getTimeoutStage() {
@@ -218,6 +221,7 @@
     /**
      * A builder for {@link HotwordTrainingData}
      */
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
     @SuppressWarnings("WeakerAccess")
     @DataClass.Generated.Member
     public static final class Builder {
@@ -251,7 +255,7 @@
 
         /**
          * App-defined stage when hotword model timed-out while running.
-         * <p> Returns 0 if unset.
+         * <p> Returns -1 if unset.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setTimeoutStage(int value) {
@@ -287,7 +291,7 @@
     }
 
     @DataClass.Generated(
-            time = 1696092128091L,
+            time = 1697826948280L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java",
             inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudioList\nprivate final  int mTimeoutStage\npublic static  int getMaxTrainingDataBytes()\nprivate static  java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudioList()\nprivate static  int defaultTimeoutStage()\nprivate  void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
diff --git a/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
new file mode 100644
index 0000000..7eb5280
--- /dev/null
+++ b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
@@ -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 android.service.voice;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Enforces daily limits on the egress of {@link HotwordTrainingData} from the hotword detection
+ * service.
+ *
+ * <p> Egress is tracked across UTC day (24-hour window) and count is reset at
+ * midnight (UTC 00:00:00).
+ *
+ * @hide
+ */
+public class HotwordTrainingDataLimitEnforcer {
+    private static final String TAG = "HotwordTrainingDataLimitEnforcer";
+
+    /**
+     * Number of hotword training data events that are allowed to be egressed per day.
+     */
+    private static final int TRAINING_DATA_EGRESS_LIMIT = 20;
+
+    /**
+     * Name of hotword training data limit shared preference.
+     */
+    private static final String TRAINING_DATA_LIMIT_SHARED_PREF = "TrainingDataSharedPref";
+
+    /**
+     * Key for date associated with
+     * {@link HotwordTrainingDataLimitEnforcer#TRAINING_DATA_EGRESS_COUNT}.
+     */
+    private static final String TRAINING_DATA_EGRESS_DATE = "TRAINING_DATA_EGRESS_DATE";
+
+    /**
+     * Key for number of hotword training data events egressed on
+     * {@link HotwordTrainingDataLimitEnforcer#TRAINING_DATA_EGRESS_DATE}.
+     */
+    private static final String TRAINING_DATA_EGRESS_COUNT = "TRAINING_DATA_EGRESS_COUNT";
+
+    private SharedPreferences mSharedPreferences;
+
+    private static final Object INSTANCE_LOCK = new Object();
+    private final Object mTrainingDataIncrementLock = new Object();
+
+    private static HotwordTrainingDataLimitEnforcer sInstance;
+
+    /** Get singleton HotwordTrainingDataLimitEnforcer instance. */
+    public static @NonNull HotwordTrainingDataLimitEnforcer getInstance(@NonNull Context context) {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new HotwordTrainingDataLimitEnforcer(context.getApplicationContext());
+            }
+            return sInstance;
+        }
+    }
+
+    private HotwordTrainingDataLimitEnforcer(Context context) {
+        mSharedPreferences = context.getSharedPreferences(
+                new File(Environment.getDataSystemCeDirectory(UserHandle.USER_SYSTEM),
+                        TRAINING_DATA_LIMIT_SHARED_PREF),
+                Context.MODE_PRIVATE);
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public void resetTrainingDataEgressCount() {
+        Log.i(TAG, "Resetting training data egress count!");
+        synchronized (mTrainingDataIncrementLock) {
+            // Clear all training data shared preferences.
+            mSharedPreferences.edit().clear().commit();
+        }
+    }
+
+    /**
+     * Increments training data egress count.
+     * <p> If count exceeds daily training data egress limit, returns false. Else, will return true.
+     */
+    public boolean incrementEgressCount() {
+        synchronized (mTrainingDataIncrementLock) {
+            return incrementTrainingDataEgressCountLocked();
+        }
+    }
+
+    private boolean incrementTrainingDataEgressCountLocked() {
+        LocalDate utcDate = LocalDate.now(ZoneOffset.UTC);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        String currentDate = utcDate.format(formatter);
+
+        String storedDate = mSharedPreferences.getString(TRAINING_DATA_EGRESS_DATE, "");
+        int storedCount = mSharedPreferences.getInt(TRAINING_DATA_EGRESS_COUNT, 0);
+        Log.i(TAG,
+                TextUtils.formatSimple("There are %s hotword training data events egressed for %s",
+                        storedCount, storedDate));
+
+        SharedPreferences.Editor editor = mSharedPreferences.edit();
+
+        // If date has not changed from last training data event, increment counter if within
+        // limit.
+        if (storedDate.equals(currentDate)) {
+            if (storedCount < TRAINING_DATA_EGRESS_LIMIT) {
+                Log.i(TAG, "Within hotword training data egress limit, incrementing...");
+                editor.putInt(TRAINING_DATA_EGRESS_COUNT, storedCount + 1);
+                editor.commit();
+                return true;
+            }
+            Log.i(TAG, "Exceeded hotword training data egress limit.");
+            return false;
+        }
+
+        // If date has changed, reset.
+        Log.i(TAG, TextUtils.formatSimple(
+                "Stored date %s is different from current data %s. Resetting counters...",
+                storedDate, currentDate));
+
+        editor.putString(TRAINING_DATA_EGRESS_DATE, currentDate);
+        editor.putInt(TRAINING_DATA_EGRESS_COUNT, 1);
+        editor.commit();
+        return true;
+    }
+}
diff --git a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
index c6b10ff..a9c6af79 100644
--- a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
+++ b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
@@ -18,6 +18,7 @@
 
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.HotwordTrainingData;
 
 /**
  * Callback for returning the detected result from the HotwordDetectionService.
@@ -37,4 +38,10 @@
      * Sends {@code result} to the HotwordDetector.
      */
     void onRejected(in HotwordRejectedResult result);
+
+    /**
+     * Called by {@link HotwordDetectionService} to egress training data to the
+     * {@link HotwordDetector}.
+     */
+     void onTrainingData(in HotwordTrainingData data);
 }
diff --git a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl
index fab830a..6226772 100644
--- a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl
+++ b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl
@@ -20,6 +20,7 @@
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.HotwordTrainingData;
 
 /**
  * Callback for returning the detected result from the HotwordDetectionService.
@@ -47,4 +48,10 @@
      */
     void onRejected(
         in HotwordRejectedResult hotwordRejectedResult);
+
+    /**
+     * Called by {@link HotwordDetectionService} to egress training data to the
+     * {@link HotwordDetector}.
+     */
+     void onTrainingData(in HotwordTrainingData data);
 }
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index f1bc792..2c68fae 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.RECORD_AUDIO;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.hardware.soundtrigger.SoundTrigger;
@@ -201,6 +202,13 @@
                         result != null ? result : new HotwordRejectedResult.Builder().build());
             }));
         }
+
+        @Override
+        public void onTrainingData(@NonNull HotwordTrainingData result) {
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
+                mCallback.onTrainingData(result);
+            }));
+        }
     }
 
     private static class InitializationStateListener
@@ -238,6 +246,13 @@
         }
 
         @Override
+        public void onTrainingData(@NonNull HotwordTrainingData data) {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onTrainingData event");
+            }
+        }
+
+        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b5448d4..91de894 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -369,6 +369,12 @@
                 Slog.i(TAG, "Ignored #onRejected event");
             }
         }
+        @Override
+        public void onTrainingData(HotwordTrainingData data) throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "Ignored #onTrainingData event");
+            }
+        }
 
         @Override
         public void onRecognitionPaused() throws RemoteException {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 3f41c56..42203d4 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -48,6 +49,7 @@
 import android.os.SharedMemory;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.service.voice.flags.Flags;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -443,6 +445,20 @@
         }
     }
 
+    /** Reset hotword training data egressed count.
+     *  @hide */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+    @RequiresPermission(Manifest.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT)
+    public final void resetHotwordTrainingDataEgressCountForTest() {
+        Log.i(TAG, "Resetting hotword training data egress count for test.");
+        try {
+            mSystemService.resetHotwordTrainingDataEgressCountForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
      * This instance must be retained and used by the client.
@@ -520,7 +536,7 @@
             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -546,6 +562,10 @@
             @NonNull SoundTrigger.ModuleProperties moduleProperties,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale,
+        // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the
+        // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -612,6 +632,11 @@
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+        // AlwaysOnHotwordDetector.Callback)} and replace with the permission
+        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
+
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
                 /* modulProperties */ null, /* executor= */ null, callback);
@@ -663,7 +688,11 @@
             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+        // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission
+        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -690,6 +719,10 @@
             @NonNull SoundTrigger.ModuleProperties moduleProperties,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle,
+        // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+        // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index cabcae3..d40b39e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1809,7 +1809,7 @@
      * in milliseconds of the KeyEvent that triggered Assistant and
      * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
      *  referring to the device that sent the request. Starting from Android 14, the system will
-     * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
+     * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
      * application should handle null case before Android 14.
      * @param showFlags The show flags originally provided to
      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index bb5dd7f..35834fd 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -536,7 +536,16 @@
     @MainThread
     public void setRecognitionListener(RecognitionListener listener) {
         checkIsCalledFromMainThread();
-        putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
+        if (mListener.mInternalListener == null) {
+            // This shortcut is needed because otherwise, if there's an error connecting, it never
+            // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does
+            // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same
+            // place does not get forwarded anywhere.
+            // Thread-wise, this is safe as both this method and the handler are on the UI thread.
+            handleChangeListener(listener);
+        } else {
+            putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
+        }
     }
 
     /**
@@ -758,7 +767,7 @@
         try {
             mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource());
             if (DBG) Log.d(TAG, "service start listening command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "startListening() failed", e);
             mListener.onError(ERROR_CLIENT);
         }
@@ -772,7 +781,7 @@
         try {
             mService.stopListening(mListener);
             if (DBG) Log.d(TAG, "service stop listening command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "stopListening() failed", e);
             mListener.onError(ERROR_CLIENT);
         }
@@ -786,7 +795,7 @@
         try {
             mService.cancel(mListener, /*isShutdown*/ false);
             if (DBG) Log.d(TAG, "service cancel command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "cancel() failed", e);
             mListener.onError(ERROR_CLIENT);
         }
@@ -821,7 +830,7 @@
                     mContext.getAttributionSource(),
                     new InternalSupportCallback(callbackExecutor, recognitionSupportCallback));
             if (DBG) Log.d(TAG, "service support command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "checkRecognitionSupport() failed", e);
             callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT));
         }
@@ -841,7 +850,7 @@
                 mService.triggerModelDownload(
                         recognizerIntent, mContext.getAttributionSource(), null);
                 if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
-            } catch (final RemoteException e) {
+            } catch (final Exception e) {
                 Log.e(TAG, "triggerModelDownload() without a listener failed", e);
                 mListener.onError(ERROR_CLIENT);
             }
@@ -853,7 +862,7 @@
                         recognizerIntent, mContext.getAttributionSource(),
                         new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
                 if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
-            } catch (final RemoteException e) {
+            } catch (final Exception e) {
                 Log.e(TAG, "triggerModelDownload() with a listener failed", e);
                 callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
             }
@@ -880,7 +889,7 @@
         if (mService != null) {
             try {
                 mService.cancel(mListener, /*isShutdown*/ true);
-            } catch (final RemoteException e) {
+            } catch (final Exception e) {
                 // Not important
             }
         }
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 65a1da6..4c81888 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -190,7 +190,8 @@
             @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
             boolean useFallbackLineSpacing) {
         return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad,
-                ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */);
+                ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */,
+                null /* minimumFontMetrics */);
     }
 
     /** @hide */
@@ -199,7 +200,8 @@
             @NonNull Alignment align, float spacingMultiplier, float spacingAmount,
             @NonNull BoringLayout.Metrics metrics, boolean includePad,
             @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
-            boolean useFallbackLineSpacing, boolean useBoundsForWidth) {
+            boolean useFallbackLineSpacing, boolean useBoundsForWidth,
+            @Nullable Paint.FontMetrics minimumFontMetrics) {
         boolean trust;
 
         if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
@@ -270,7 +272,8 @@
                 spacingAdd, includePad, false /* fallbackLineSpacing */,
                 outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */,
                 BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
-                null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+                null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false,
+                null);
 
         mEllipsizedWidth = outerwidth;
         mEllipsizedStart = 0;
@@ -343,7 +346,7 @@
                 ellipsizedWidth, ellipsize, 1 /* maxLines */,
                 BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
                 null /* rightIndents */, JUSTIFICATION_MODE_NONE,
-                LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */);
+                LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null);
     }
 
     /** @hide */
@@ -359,12 +362,13 @@
             int ellipsizedWidth,
             TextUtils.TruncateAt ellipsize,
             Metrics metrics,
-            boolean useBoundsForWidth) {
+            boolean useBoundsForWidth,
+            @Nullable Paint.FontMetrics minimumFontMetrics) {
         this(text, paint, width, align, TextDirectionHeuristics.LTR,
                 spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth,
                 ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE,
                 Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE,
-                LineBreakConfig.NONE, metrics, useBoundsForWidth);
+                LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics);
     }
 
     /* package */ BoringLayout(
@@ -387,12 +391,13 @@
             int justificationMode,
             LineBreakConfig lineBreakConfig,
             Metrics metrics,
-            boolean useBoundsForWidth) {
+            boolean useBoundsForWidth,
+            @Nullable Paint.FontMetrics minimumFontMetrics) {
 
         super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
                 fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
                 hyphenationFrequency, leftIndents, rightIndents, justificationMode,
-                lineBreakConfig, useBoundsForWidth);
+                lineBreakConfig, useBoundsForWidth, minimumFontMetrics);
 
 
         boolean trust;
@@ -548,6 +553,15 @@
     public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
             @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
             @Nullable Metrics metrics) {
+        return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics);
+    }
+
+    /**
+     * @hide
+     */
+    public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
+            @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
+            @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
         final int textLength = text.length();
         if (hasAnyInterestingChars(text, textLength)) {
            return null;  // There are some interesting characters. Not boring.
@@ -570,6 +584,19 @@
             fm.reset();
         }
 
+        if (ClientFlags.fixLineHeightForLocale()) {
+            if (minimumFontMetrics == null) {
+                paint.getFontMetricsIntForLocale(fm);
+            } else {
+                fm.set(minimumFontMetrics);
+                // Because the font metrics is provided by public APIs, adjust the top/bottom with
+                // ascent/descent: top must be smaller than ascent, bottom must be larger than
+                // descent.
+                fm.top = Math.min(fm.top, fm.ascent);
+                fm.bottom = Math.max(fm.bottom, fm.descent);
+            }
+        }
+
         TextLine line = TextLine.obtain();
         line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
                 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 46fa501..0421d5a 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -27,14 +27,6 @@
  * @hide
  */
 public class ClientFlags {
-
-    /**
-     * @see Flags#deprecateFontsXml()
-     */
-    public static boolean deprecateFontsXml() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML);
-    }
-
     /**
      * @see Flags#noBreakNoHyphenationSpan()
      */
@@ -55,4 +47,11 @@
     public static boolean useBoundsForWidth() {
         return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
     }
+
+    /**
+     * @see Flags#fixLineHeightForLocale()
+     */
+    public static boolean fixLineHeightForLocale() {
+        return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
+    }
 }
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 2b3a081..7b9cb6a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,6 +16,11 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -280,6 +285,7 @@
          * @see android.widget.TextView#setLineBreakWordStyle
          */
         @NonNull
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
             mLineBreakConfig = lineBreakConfig;
             return this;
@@ -303,12 +309,50 @@
          * @see Layout.Builder#setUseBoundsForWidth(boolean)
          */
         @NonNull
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
             return this;
         }
 
         /**
+         * Set the minimum font metrics used for line spacing.
+         *
+         * <p>
+         * {@code null} is the default value. If {@code null} is set or left as default, the
+         * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+         * used.
+         *
+         * <p>
+         * The minimum meaning here is the minimum value of line spacing: maximum value of
+         * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+         *
+         * <p>
+         * By setting this value, each line will have minimum line spacing regardless of the text
+         * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+         * script. By setting the metrics obtained by
+         * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+         * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+         * if the text is an English text. If the vertical metrics of the text is larger than
+         * Japanese, for example Burmese, the bigger font metrics is used.
+         *
+         * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+         *                          value obtained by
+         *                          {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+         * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+         * @see android.widget.TextView#getMinimumFontMetrics()
+         * @see Layout#getMinimumFontMetrics()
+         * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+         * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+         */
+        @NonNull
+        @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+        public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+            mMinimumFontMetrics = minimumFontMetrics;
+            return this;
+        }
+
+        /**
          * Build the {@link DynamicLayout} after options have been set.
          *
          * <p>Note: the builder object must not be reused in any way after calling this method.
@@ -341,6 +385,7 @@
         private int mEllipsizedWidth;
         private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
         private boolean mUseBoundsForWidth;
+        private @Nullable Paint.FontMetrics mMinimumFontMetrics;
 
         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 
@@ -416,7 +461,7 @@
                 false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize,
                 Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency,
                 null /* leftIndents */, null /* rightIndents */, justificationMode,
-                lineBreakConfig, false /* useBoundsForWidth */);
+                lineBreakConfig, false /* useBoundsForWidth */, null /* minimumFontMetrics */);
 
         final Builder b = Builder.obtain(base, paint, width)
                 .setAlignment(align)
@@ -442,7 +487,7 @@
                 b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
                 Integer.MAX_VALUE /* maxLines */, b.mBreakStrategy, b.mHyphenationFrequency,
                 null /* leftIndents */, null /* rightIndents */, b.mJustificationMode,
-                b.mLineBreakConfig, b.mUseBoundsForWidth);
+                b.mLineBreakConfig, b.mUseBoundsForWidth, b.mMinimumFontMetrics);
 
         mDisplay = b.mDisplay;
         mIncludePad = b.mIncludePad;
@@ -470,6 +515,7 @@
         mBase = b.mBase;
         mFallbackLineSpacing = b.mFallbackLineSpacing;
         mUseBoundsForWidth = b.mUseBoundsForWidth;
+        mMinimumFontMetrics = b.mMinimumFontMetrics;
         if (b.mEllipsize != null) {
             mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
             mEllipsizedWidth = b.mEllipsizedWidth;
@@ -666,6 +712,7 @@
                 .setAddLastLineLineSpacing(!islast)
                 .setIncludePad(false)
                 .setUseBoundsForWidth(mUseBoundsForWidth)
+                .setMinimumFontMetrics(mMinimumFontMetrics)
                 .setCalculateBounds(true);
 
         reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
@@ -1268,7 +1315,7 @@
     }
 
     /**
-     * Gets the {@link LineBreakconfig} used in this DynamicLayout.
+     * Gets the {@link LineBreakConfig} used in this DynamicLayout.
      * Use this only to consult the LineBreakConfig's properties and not
      * to change them.
      *
@@ -1318,6 +1365,7 @@
     private Rect mTempRect = new Rect();
 
     private boolean mUseBoundsForWidth;
+    @Nullable Paint.FontMetrics mMinimumFontMetrics;
 
     @UnsupportedAppUsage
     private static StaticLayout sStaticLayout = null;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4f4dea7..47c29d9 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
 
 package android.text;
 
+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 android.annotation.FlaggedApi;
@@ -287,7 +288,7 @@
         this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
                 spacingMult, spacingAdd, false, false, 0, null, Integer.MAX_VALUE,
                 BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null, null,
-                JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+                JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, null);
     }
 
     /**
@@ -336,7 +337,8 @@
             int[] rightIndents,
             int justificationMode,
             LineBreakConfig lineBreakConfig,
-            boolean useBoundsForWidth
+            boolean useBoundsForWidth,
+            Paint.FontMetrics minimumFontMetrics
     ) {
 
         if (width < 0)
@@ -371,6 +373,7 @@
         mJustificationMode = justificationMode;
         mLineBreakConfig = lineBreakConfig;
         mUseBoundsForWidth = useBoundsForWidth;
+        mMinimumFontMetrics = minimumFontMetrics;
     }
 
     /**
@@ -3332,6 +3335,7 @@
     private int mJustificationMode;
     private LineBreakConfig mLineBreakConfig;
     private boolean mUseBoundsForWidth;
+    private @Nullable Paint.FontMetrics mMinimumFontMetrics;
 
     /** @hide */
     @IntDef(prefix = { "DIR_" }, value = {
@@ -3787,12 +3791,48 @@
             return this;
         }
 
+        /**
+         * Set the minimum font metrics used for line spacing.
+         *
+         * <p>
+         * {@code null} is the default value. If {@code null} is set or left it as default, the font
+         * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+         *
+         * <p>
+         * The minimum meaning here is the minimum value of line spacing: maximum value of
+         * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+         *
+         * <p>
+         * By setting this value, each line will have minimum line spacing regardless of the text
+         * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+         * script. By setting the metrics obtained by
+         * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+         * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+         * if the text is an English text. If the vertical metrics of the text is larger than
+         * Japanese, for example Burmese, the bigger font metrics is used.
+         *
+         * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+         *                          value obtained by
+         *                          {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+         * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+         * @see android.widget.TextView#getMinimumFontMetrics()
+         * @see Layout#getMinimumFontMetrics()
+         * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+         * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+         */
+        @NonNull
+        @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+        public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+            mMinimumFontMetrics = minimumFontMetrics;
+            return this;
+        }
+
         private BoringLayout.Metrics isBoring() {
             if (mStart != 0 || mEnd != mText.length()) {  // BoringLayout only support entire text.
                 return null;
             }
             BoringLayout.Metrics metrics = BoringLayout.isBoring(mText, mPaint, mTextDir,
-                    mFallbackLineSpacing, null);
+                    mFallbackLineSpacing, mMinimumFontMetrics, null);
             if (metrics == null) {
                 return null;
             }
@@ -3833,7 +3873,8 @@
                         mText, mPaint, mWidth, mAlignment, mTextDir, mSpacingMult, mSpacingAdd,
                         mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines,
                         mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents,
-                        mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth);
+                        mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth,
+                        mMinimumFontMetrics);
             }
         }
 
@@ -3858,6 +3899,7 @@
         private int mJustificationMode = JUSTIFICATION_MODE_NONE;
         private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
         private boolean mUseBoundsForWidth;
+        private Paint.FontMetrics mMinimumFontMetrics;
     }
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -4164,4 +4206,22 @@
     public boolean getUseBoundsForWidth() {
         return mUseBoundsForWidth;
     }
+
+    /**
+     * Get the minimum font metrics used for line spacing.
+     *
+     * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see android.widget.TextView#getMinimumFontMetrics()
+     * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     *
+     * @return a minimum font metrics. {@code null} for using the value obtained by
+     *         {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+     */
+    @Nullable
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public Paint.FontMetrics getMinimumFontMetrics() {
+        return mMinimumFontMetrics;
+    }
 }
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index c1d0e9b9..ac5eb3c 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -16,6 +16,9 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -416,10 +419,12 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public interface StyleRunCallback {
         /**
          * Called when a single style run is identified.
          */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         void onAppendStyleRun(@NonNull Paint paint,
                 @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length,
                 boolean isRtl);
@@ -427,6 +432,7 @@
         /**
          * Called when a single replacement run is identified.
          */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         void onAppendReplacementRun(@NonNull Paint paint,
                 @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width);
     }
@@ -488,6 +494,7 @@
     @SuppressLint("ExecutorRegistration")
     @TestApi
     @NonNull
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static MeasuredParagraph buildForStaticLayoutTest(
             @NonNull TextPaint paint,
             @Nullable LineBreakConfig lineBreakConfig,
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 517ae4f..5f6a9bd 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -457,12 +457,21 @@
         } else {
             hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
         }
+        LineBreakConfig config = params.getLineBreakConfig();
+        if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                && pct.getParagraphCount() != 1) {
+            // If the text has multiple paragraph, resolve line break word style auto to none.
+            config = new LineBreakConfig.Builder()
+                    .merge(config)
+                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                    .build();
+        }
         ArrayList<ParagraphInfo> result = new ArrayList<>();
         for (int i = 0; i < pct.getParagraphCount(); ++i) {
             final int paraStart = pct.getParagraphStart(i);
             final int paraEnd = pct.getParagraphEnd(i);
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd,
+                    params.getTextPaint(), config, pct, paraStart, paraEnd,
                     params.getTextDirection(), hyphenationMode, computeLayout, true,
                     pct.getMeasuredParagraph(i), null /* no recycle */)));
         }
@@ -489,6 +498,7 @@
             hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
         }
 
+        LineBreakConfig config = null;
         int paraEnd = 0;
         for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
             paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
@@ -500,8 +510,21 @@
                 paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
             }
 
+            if (config == null) {
+                config = params.getLineBreakConfig();
+                if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                        && !(paraStart == start && paraEnd == end)) {
+                    // If the text has multiple paragraph, resolve line break word style auto to
+                    // none.
+                    config = new LineBreakConfig.Builder()
+                            .merge(config)
+                            .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                            .build();
+                }
+            }
+
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd,
+                    params.getTextPaint(), config, text, paraStart, paraEnd,
                     params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
                     null /* no hint */,
                     null /* no recycle */)));
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index e3c72c9..77e616b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,10 @@
 
 package android.text;
 
+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 android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -439,6 +443,7 @@
          * @see Layout.Builder#setUseBoundsForWidth(boolean)
          */
         @NonNull
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
             return this;
@@ -456,6 +461,43 @@
         }
 
         /**
+         * Set the minimum font metrics used for line spacing.
+         *
+         * <p>
+         * {@code null} is the default value. If {@code null} is set or left as default, the
+         * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+         * used.
+         *
+         * <p>
+         * The minimum meaning here is the minimum value of line spacing: maximum value of
+         * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+         *
+         * <p>
+         * By setting this value, each line will have minimum line spacing regardless of the text
+         * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+         * script. By setting the metrics obtained by
+         * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+         * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+         * if the text is an English text. If the vertical metrics of the text is larger than
+         * Japanese, for example Burmese, the bigger font metrics is used.
+         *
+         * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+         *                          value obtained by
+         *                          {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+         * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+         * @see android.widget.TextView#getMinimumFontMetrics()
+         * @see Layout#getMinimumFontMetrics()
+         * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+         * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+         */
+        @NonNull
+        @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+        public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+            mMinimumFontMetrics = minimumFontMetrics;
+            return this;
+        }
+
+        /**
          * Build the {@link StaticLayout} after options have been set.
          *
          * <p>Note: the builder object must not be reused in any way after calling this
@@ -516,6 +558,7 @@
         private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
         private boolean mUseBoundsForWidth;
         private boolean mCalculateBounds;
+        @Nullable private Paint.FontMetrics mMinimumFontMetrics;
 
         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 
@@ -546,7 +589,8 @@
                 null,  // rightIndents
                 JUSTIFICATION_MODE_NONE,
                 null,  // lineBreakConfig,
-                false  // useBoundsForWidth
+                false,  // useBoundsForWidth
+                null  // minimumFontMetrics
         );
 
         mColumns = COLUMNS_ELLIPSIZE;
@@ -623,7 +667,8 @@
                 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
                 b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
                 b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
-                b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth);
+                b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth,
+                b.mMinimumFontMetrics);
 
         mColumns = columnSize;
         if (b.mEllipsize != null) {
@@ -707,6 +752,35 @@
             indents = null;
         }
 
+        int defaultTop;
+        int defaultAscent;
+        int defaultDescent;
+        int defaultBottom;
+        if (ClientFlags.fixLineHeightForLocale()) {
+            if (b.mMinimumFontMetrics != null) {
+                defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
+                defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
+                defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
+                defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
+            } else {
+                paint.getFontMetricsIntForLocale(fm);
+                defaultTop = fm.top;
+                defaultAscent = fm.ascent;
+                defaultDescent = fm.descent;
+                defaultBottom = fm.bottom;
+            }
+
+            // Because the font metrics is provided by public APIs, adjust the top/bottom with
+            // ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
+            defaultTop = Math.min(defaultTop, defaultAscent);
+            defaultBottom = Math.max(defaultBottom, defaultDescent);
+        } else {
+            defaultTop = 0;
+            defaultAscent = 0;
+            defaultDescent = 0;
+            defaultBottom = 0;
+        }
+
         final LineBreaker lineBreaker = new LineBreaker.Builder()
                 .setBreakStrategy(b.mBreakStrategy)
                 .setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
@@ -885,7 +959,10 @@
             // measuring
             int here = paraStart;
 
-            int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+            int fmTop = defaultTop;
+            int fmBottom = defaultBottom;
+            int fmAscent = defaultAscent;
+            int fmDescent = defaultDescent;
             int fmCacheIndex = 0;
             int spanEndCacheIndex = 0;
             int breakIndex = 0;
@@ -978,7 +1055,15 @@
                 && mLineCount < mMaximumVisibleLineCount) {
             final MeasuredParagraph measuredPara =
                     MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
-            paint.getFontMetricsInt(fm);
+            if (ClientFlags.fixLineHeightForLocale()) {
+                fm.top = defaultTop;
+                fm.ascent = defaultAscent;
+                fm.descent = defaultDescent;
+                fm.bottom = defaultBottom;
+            } else {
+                paint.getFontMetricsInt(fm);
+            }
+
             v = out(source,
                     bufEnd, bufEnd, fm.ascent, fm.descent,
                     fm.top, fm.bottom,
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 536e3cc..2466386 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -55,10 +55,22 @@
      * List of text flags to be transferred to the application process.
      */
     public static final String[] TEXT_ACONFIGS_FLAGS = {
-            Flags.FLAG_DEPRECATE_FONTS_XML,
             Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
             Flags.FLAG_PHRASE_STRICT_FALLBACK,
             Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
+            Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
+    };
+
+    /**
+     * List of the default values of the text flags.
+     *
+     * The order must be the same to the TEXT_ACONFIG_FLAGS.
+     */
+    public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
+            Flags.noBreakNoHyphenationSpan(),
+            Flags.phraseStrictFallback(),
+            Flags.useBoundsForWidth(),
+            Flags.fixLineHeightForLocale(),
     };
 
     /**
diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java
index be002f3..b0a70ea 100644
--- a/core/java/android/text/WordSegmentFinder.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -24,7 +24,7 @@
 
 /**
  * Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
- * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
+ * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
  * any text segments.
  *
  * <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig
deleted file mode 100644
index 04e64f9..0000000
--- a/core/java/android/text/flags/custom_locale_fallback.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
-  name: "custom_locale_fallback"
-  namespace: "text"
-  description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
-  bug: ""
-}
diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig
deleted file mode 100644
index 58dc210..0000000
--- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
-  name: "deprecate_fonts_xml"
-  namespace: "text"
-  description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
-  bug: "281769620"
-}
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
deleted file mode 100644
index b0aa72a..0000000
--- a/core/java/android/text/flags/fix_double_underline.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
-  name: "fix_double_underline"
-  namespace: "text"
-  description: "Feature flag for fixing double underline because of the multiple font used in the single line."
-  bug: "297336724"
-}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
new file mode 100644
index 0000000..43c38f3
--- /dev/null
+++ b/core/java/android/text/flags/flags.aconfig
@@ -0,0 +1,77 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "vendor_custom_locale_fallback"
+  namespace: "text"
+  description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
+  is_fixed_read_only: true
+  bug: "278768958"
+}
+
+flag {
+  name: "new_fonts_fallback_xml"
+  namespace: "text"
+  description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
+  # Make read only, as it could be used before the Settings provider is initialized.
+  is_fixed_read_only: true
+  bug: "281769620"
+}
+
+flag {
+  name: "fix_double_underline"
+  namespace: "text"
+  description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+  bug: "297336724"
+}
+
+flag {
+  name: "fix_line_height_for_locale"
+  namespace: "text"
+  description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
+  bug: "303326708"
+}
+
+flag {
+  name: "no_break_no_hyphenation_span"
+  namespace: "text"
+  description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+  bug: "283193586"
+}
+
+flag {
+  name: "use_optimized_boottime_font_loading"
+  namespace: "text"
+  description: "Feature flag ensuring that font is loaded once and asynchronously."
+  # Make read only, as font loading is in the critical boot path which happens before the read-write
+  # flags propagate to the device.
+  is_fixed_read_only: true
+  bug: "304406888"
+}
+
+flag {
+  name: "phrase_strict_fallback"
+  namespace: "text"
+  description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+  bug: "281970875"
+}
+
+flag {
+  name: "use_bounds_for_width"
+  namespace: "text"
+  description: "Feature flag for preventing horizontal clipping."
+  bug: "63938206"
+}
+
+flag {
+  name: "deprecate_ui_fonts"
+  namespace: "text"
+  description: "Feature flag for deprecating UI fonts. By setting true for this feature flag, the elegant text height of will be turned on by default unless explicitly setting it to false by attribute or Java API call."
+  bug: "279646685"
+}
+
+flag {
+  name: "word_style_auto"
+  namespace: "text"
+  description: "A feature flag that implements line break word style auto."
+  bug: "280005585"
+}
diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
deleted file mode 100644
index 60f1e88..0000000
--- a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
-  name: "no_break_no_hyphenation_span"
-  namespace: "text"
-  description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
-  bug: "283193586"
-}
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
deleted file mode 100644
index c67a21b..0000000
--- a/core/java/android/text/flags/phrase_strict_fallback.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
-  name: "phrase_strict_fallback"
-  namespace: "text"
-  description: "Feature flag for automatic fallback from phrase based line break to strict line break."
-  bug: "281970875"
-}
diff --git a/core/java/android/text/flags/use_bounds_for_width.aconfig b/core/java/android/text/flags/use_bounds_for_width.aconfig
deleted file mode 100644
index d89d5f4..0000000
--- a/core/java/android/text/flags/use_bounds_for_width.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
-  name: "use_bounds_for_width"
-  namespace: "text"
-  description: "Feature flag for preventing horizontal clipping."
-  bug: "63938206"
-}
diff --git a/core/java/android/util/Base64.java b/core/java/android/util/Base64.java
index 92abd7c..33cc5e3 100644
--- a/core/java/android/util/Base64.java
+++ b/core/java/android/util/Base64.java
@@ -26,6 +26,7 @@
  * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
  * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
  */
+@android.ravenwood.annotations.RavenwoodWholeClassKeep
 public class Base64 {
     /**
      * Default values for encoder/decoder flags.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2906d86..766e924 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -200,6 +200,12 @@
     public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
             "settings_remote_device_credential_validation";
 
+    /**
+     * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
+     * @hide
+     */
+    public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
+            "settings_treat_pause_as_quarantine";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -247,6 +253,7 @@
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+        DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -264,6 +271,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
         PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
+        PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
     }
 
     /**
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 1ed5d3f..71d382e 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -92,6 +92,12 @@
      * SurfaceView Surface, the buffer producer will already have access to the transform hint and
      * no additional work is needed.
      *
+     * If the root surface is not available, the API will return {@code BUFFER_TRANSFORM_IDENTITY}.
+     * The caller should register a listener to listen for any changes. @see
+     * {@link #addOnBufferTransformHintChangedListener(OnBufferTransformHintChangedListener)}.
+     * Warning: Calling this API in Android 14 (API Level 34) or earlier will crash if the root
+     * surface is not available.
+     *
      * @see HardwareBuffer
      */
     default @SurfaceControl.BufferTransform int getBufferTransformHint() {
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 1ab055a..a74cbe4 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,9 +16,11 @@
 
 package android.view;
 
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -695,6 +697,7 @@
      */
     @TestApi
     @UnsupportedAppUsage
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
     public long getFrameTimeNanos() {
         synchronized (mLock) {
             if (!mCallbacksRunning) {
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index a89f795..dc41b70 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -52,6 +52,12 @@
      */
     public static final int RECORD_CONTENT_TASK = 1;
 
+    /** Full screen sharing (app is not selected). */
+    public static final int TARGET_UID_FULL_SCREEN = -1;
+
+    /** Can't report (e.g. side loaded app). */
+    public static final int TARGET_UID_UNKNOWN = -2;
+
     /**
      * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
      * recorded content rendered to its surface.
@@ -89,27 +95,36 @@
      */
     private boolean mWaitingForConsent = false;
 
+    /** UID of the package that is captured if selected. */
+    private int mTargetUid = TARGET_UID_UNKNOWN;
+
     /**
      * Default instance, with recording the display.
      */
     private ContentRecordingSession() {
     }
 
-    /**
-     * Returns an instance initialized for recording the indicated display.
-     */
+    /** Returns an instance initialized for recording the indicated display. */
     public static ContentRecordingSession createDisplaySession(int displayToMirror) {
-        return new ContentRecordingSession().setDisplayToRecord(displayToMirror)
-                .setContentToRecord(RECORD_CONTENT_DISPLAY);
+        return new ContentRecordingSession()
+                .setDisplayToRecord(displayToMirror)
+                .setContentToRecord(RECORD_CONTENT_DISPLAY)
+                .setTargetUid(TARGET_UID_FULL_SCREEN);
     }
 
-    /**
-     * Returns an instance initialized for task recording.
-     */
+    /** Returns an instance initialized for task recording. */
     public static ContentRecordingSession createTaskSession(
             @NonNull IBinder taskWindowContainerToken) {
-        return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK)
-                .setTokenToRecord(taskWindowContainerToken);
+        return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN);
+    }
+
+    /** Returns an instance initialized for task recording. */
+    public static ContentRecordingSession createTaskSession(
+            @NonNull IBinder taskWindowContainerToken, int targetUid) {
+        return new ContentRecordingSession()
+                .setContentToRecord(RECORD_CONTENT_TASK)
+                .setTokenToRecord(taskWindowContainerToken)
+                .setTargetUid(targetUid);
     }
 
     /**
@@ -175,13 +190,33 @@
         }
     }
 
+    @IntDef(prefix = "TARGET_UID_", value = {
+        TARGET_UID_FULL_SCREEN,
+        TARGET_UID_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TargetUid {}
+
+    @DataClass.Generated.Member
+    public static String targetUidToString(@TargetUid int value) {
+        switch (value) {
+            case TARGET_UID_FULL_SCREEN:
+                    return "TARGET_UID_FULL_SCREEN";
+            case TARGET_UID_UNKNOWN:
+                    return "TARGET_UID_UNKNOWN";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     /* package-private */ ContentRecordingSession(
             int virtualDisplayId,
             @RecordContent int contentToRecord,
             int displayToRecord,
             @Nullable IBinder tokenToRecord,
-            boolean waitingForConsent) {
+            boolean waitingForConsent,
+            int targetUid) {
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
 
@@ -196,6 +231,7 @@
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
         this.mWaitingForConsent = waitingForConsent;
+        this.mTargetUid = targetUid;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -251,6 +287,14 @@
     }
 
     /**
+     * UID of the package that is captured if selected.
+     */
+    @DataClass.Generated.Member
+    public int getTargetUid() {
+        return mTargetUid;
+    }
+
+    /**
      * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
      * recorded content rendered to its surface.
      */
@@ -314,6 +358,15 @@
         return this;
     }
 
+    /**
+     * UID of the package that is captured if selected.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ContentRecordingSession setTargetUid( int value) {
+        mTargetUid = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -325,7 +378,8 @@
                 "contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
                 "displayToRecord = " + mDisplayToRecord + ", " +
                 "tokenToRecord = " + mTokenToRecord + ", " +
-                "waitingForConsent = " + mWaitingForConsent +
+                "waitingForConsent = " + mWaitingForConsent + ", " +
+                "targetUid = " + mTargetUid +
         " }";
     }
 
@@ -346,7 +400,8 @@
                 && mContentToRecord == that.mContentToRecord
                 && mDisplayToRecord == that.mDisplayToRecord
                 && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
-                && mWaitingForConsent == that.mWaitingForConsent;
+                && mWaitingForConsent == that.mWaitingForConsent
+                && mTargetUid == that.mTargetUid;
     }
 
     @Override
@@ -361,6 +416,7 @@
         _hash = 31 * _hash + mDisplayToRecord;
         _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
         _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent);
+        _hash = 31 * _hash + mTargetUid;
         return _hash;
     }
 
@@ -378,6 +434,7 @@
         dest.writeInt(mContentToRecord);
         dest.writeInt(mDisplayToRecord);
         if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord);
+        dest.writeInt(mTargetUid);
     }
 
     @Override
@@ -397,6 +454,7 @@
         int contentToRecord = in.readInt();
         int displayToRecord = in.readInt();
         IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder();
+        int targetUid = in.readInt();
 
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
@@ -412,6 +470,7 @@
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
         this.mWaitingForConsent = waitingForConsent;
+        this.mTargetUid = targetUid;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -442,6 +501,7 @@
         private int mDisplayToRecord;
         private @Nullable IBinder mTokenToRecord;
         private boolean mWaitingForConsent;
+        private int mTargetUid;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -513,10 +573,21 @@
             return this;
         }
 
+        /**
+         * UID of the package that is captured if selected.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTargetUid(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mTargetUid = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull ContentRecordingSession build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20; // Mark builder used
+            mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mVirtualDisplayId = INVALID_DISPLAY;
@@ -533,17 +604,21 @@
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mWaitingForConsent = false;
             }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mTargetUid = TARGET_UID_UNKNOWN;
+            }
             ContentRecordingSession o = new ContentRecordingSession(
                     mVirtualDisplayId,
                     mContentToRecord,
                     mDisplayToRecord,
                     mTokenToRecord,
-                    mWaitingForConsent);
+                    mWaitingForConsent,
+                    mTargetUid);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x20) != 0) {
+            if ((mBuilderFieldsSet & 0x40) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -551,10 +626,10 @@
     }
 
     @DataClass.Generated(
-            time = 1683628463074L,
+            time = 1697456140720L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
-            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\npublic static final  int TARGET_UID_FULL_SCREEN\npublic static final  int TARGET_UID_UNKNOWN\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\nprivate  int mTargetUid\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0b2b6ce..07dd882 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -18,14 +18,17 @@
 
 import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
 import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 import android.app.KeyguardManager;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -1366,7 +1369,8 @@
             // form of the larger DISPLAY_CHANGED event
             mGlobal.registerDisplayListener(toRegister, executor,
                     DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
-                            | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                            | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED,
+                    ActivityThread.currentPackageName());
         }
 
     }
@@ -1466,17 +1470,18 @@
     }
 
     /**
-     * Returns null if it's virtual display.
-     * @hide
+     * Returns the {@link OverlayProperties} of the display.
      */
-    @Nullable
+    @FlaggedApi(FLAG_OVERLAYPROPERTIES_CLASS_API)
+    @NonNull
     public OverlayProperties getOverlaySupport() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            if (mDisplayInfo.type != TYPE_VIRTUAL) {
+            if (mDisplayInfo.type == TYPE_INTERNAL
+                    || mDisplayInfo.type == TYPE_EXTERNAL) {
                 return mGlobal.getOverlaySupport();
             }
-            return null;
+            return OverlayProperties.getDefault();
         }
     }
 
@@ -2089,7 +2094,8 @@
         private final int mModeId;
         private final int mWidth;
         private final int mHeight;
-        private final float mRefreshRate;
+        private final float mPeakRefreshRate;
+        private final float mVsyncRate;
         @NonNull
         private final float[] mAlternativeRefreshRates;
         @NonNull
@@ -2101,7 +2107,15 @@
          */
         @TestApi
         public Mode(int width, int height, float refreshRate) {
-            this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
+            this(INVALID_MODE_ID, width, height, refreshRate, refreshRate, new float[0],
+                    new int[0]);
+        }
+
+        /**
+         * @hide
+         */
+        public Mode(int width, int height, float refreshRate, float vsyncRate) {
+            this(INVALID_MODE_ID, width, height, refreshRate, vsyncRate, new float[0], new int[0]);
         }
 
         /**
@@ -2109,18 +2123,29 @@
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Mode(int modeId, int width, int height, float refreshRate) {
-            this(modeId, width, height, refreshRate, new float[0], new int[0]);
+            this(modeId, width, height, refreshRate, refreshRate, new float[0], new int[0]);
         }
 
         /**
          * @hide
          */
         public Mode(int modeId, int width, int height, float refreshRate,
+                    float[] alternativeRefreshRates,
+                    @HdrCapabilities.HdrType int[] supportedHdrTypes) {
+            this(modeId, width, height, refreshRate, refreshRate, alternativeRefreshRates,
+                    supportedHdrTypes);
+        }
+
+        /**
+         * @hide
+         */
+        public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate,
                 float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
             mModeId = modeId;
             mWidth = width;
             mHeight = height;
-            mRefreshRate = refreshRate;
+            mPeakRefreshRate = refreshRate;
+            mVsyncRate = vsyncRate;
             mAlternativeRefreshRates =
                     Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
             Arrays.sort(mAlternativeRefreshRates);
@@ -2171,7 +2196,17 @@
          * Returns the refresh rate in frames per second.
          */
         public float getRefreshRate() {
-            return mRefreshRate;
+            return mPeakRefreshRate;
+        }
+
+        /**
+         * Returns the vsync rate in frames per second.
+         * The physical vsync rate may be higher than the refresh rate, as the refresh rate may be
+         * constrained by the system.
+         * @hide
+         */
+        public float getVsyncRate() {
+            return mVsyncRate;
         }
 
         /**
@@ -2217,7 +2252,7 @@
         public boolean matches(int width, int height, float refreshRate) {
             return mWidth == width &&
                     mHeight == height &&
-                    Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
+                    Float.floatToIntBits(mPeakRefreshRate) == Float.floatToIntBits(refreshRate);
         }
 
         /**
@@ -2230,9 +2265,9 @@
          *
          * @hide
          */
-        public boolean matchesIfValid(int width, int height, float refreshRate) {
+        public boolean matchesIfValid(int width, int height, float peakRefreshRate) {
             if (!isWidthValid(width) && !isHeightValid(height)
-                    && !isRefreshRateValid(refreshRate)) {
+                    && !isRefreshRateValid(peakRefreshRate)) {
                 return false;
             }
             if (isWidthValid(width) != isHeightValid(height)) {
@@ -2240,8 +2275,9 @@
             }
             return (!isWidthValid(width) || mWidth == width)
                     && (!isHeightValid(height) || mHeight == height)
-                    && (!isRefreshRateValid(refreshRate)
-                    || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate));
+                    && (!isRefreshRateValid(peakRefreshRate)
+                    || Float.floatToIntBits(mPeakRefreshRate)
+                            == Float.floatToIntBits(peakRefreshRate));
         }
 
         /**
@@ -2260,7 +2296,7 @@
          * @hide
          */
         public boolean isRefreshRateSet() {
-            return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
+            return mPeakRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
         }
 
         /**
@@ -2281,7 +2317,8 @@
                 return false;
             }
             Mode that = (Mode) other;
-            return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
+            return mModeId == that.mModeId
+                    && matches(that.mWidth, that.mHeight, that.mPeakRefreshRate)
                     && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
                     && Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
         }
@@ -2292,7 +2329,8 @@
             hash = hash * 17 + mModeId;
             hash = hash * 17 + mWidth;
             hash = hash * 17 + mHeight;
-            hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+            hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate);
+            hash = hash * 17 + Float.floatToIntBits(mVsyncRate);
             hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
             hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
             return hash;
@@ -2304,7 +2342,8 @@
                     .append("id=").append(mModeId)
                     .append(", width=").append(mWidth)
                     .append(", height=").append(mHeight)
-                    .append(", fps=").append(mRefreshRate)
+                    .append(", fps=").append(mPeakRefreshRate)
+                    .append(", vsync=").append(mVsyncRate)
                     .append(", alternativeRefreshRates=")
                     .append(Arrays.toString(mAlternativeRefreshRates))
                     .append(", supportedHdrTypes=")
@@ -2319,8 +2358,8 @@
         }
 
         private Mode(Parcel in) {
-            this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
-                    in.createIntArray());
+            this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
+                    in.createFloatArray(), in.createIntArray());
         }
 
         @Override
@@ -2328,7 +2367,8 @@
             out.writeInt(mModeId);
             out.writeInt(mWidth);
             out.writeInt(mHeight);
-            out.writeFloat(mRefreshRate);
+            out.writeFloat(mPeakRefreshRate);
+            out.writeFloat(mVsyncRate);
             out.writeFloatArray(mAlternativeRefreshRates);
             out.writeIntArray(mSupportedHdrTypes);
         }
@@ -2654,6 +2694,7 @@
             if (displayId == getDisplayId()) {
                 float newRatio = getHdrSdrRatio();
                 if (newRatio != mLastReportedRatio) {
+                    mLastReportedRatio = newRatio;
                     mListener.accept(Display.this);
                 }
             }
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 1310b0c..6b354a0 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -16,16 +16,10 @@
 
 package android.view;
 
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.view.flags.Flags;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
  *
@@ -36,16 +30,12 @@
  * methods in this class. To check if your input device ID, source, and motion axis are valid for
  * haptic feedback, you can use the
  * {@link ViewConfiguration#isHapticScrollFeedbackEnabled(int, int, int)} API.
+ *
+ * @hide
  */
-@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
 public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
     private static final String TAG = "HapticScrollFeedbackProvider";
 
-    /** @hide */
-    @IntDef(value = {MotionEvent.AXIS_SCROLL})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface HapticScrollFeedbackAxis {}
-
     private static final int TICK_INTERVAL_NO_TICK = 0;
     private static final boolean INITIAL_END_OF_LIST_HAPTICS_ENABLED = false;
 
@@ -89,8 +79,7 @@
     }
 
     @Override
-    public void onScrollProgress(
-            int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, int deltaInPixels) {
+    public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {
         maybeUpdateCurrentConfig(inputDeviceId, source, axis);
         if (!mHapticScrollFeedbackEnabled) {
             return;
@@ -117,8 +106,7 @@
     }
 
     @Override
-    public void onScrollLimit(
-            int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, boolean isStart) {
+    public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {
         maybeUpdateCurrentConfig(inputDeviceId, source, axis);
         if (!mHapticScrollFeedbackEnabled) {
             return;
@@ -135,7 +123,7 @@
     }
 
     @Override
-    public void onSnapToItem(int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis) {
+    public void onSnapToItem(int inputDeviceId, int source, int axis) {
         maybeUpdateCurrentConfig(inputDeviceId, source, axis);
         if (!mHapticScrollFeedbackEnabled) {
             return;
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514..11180ae 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -54,6 +54,10 @@
      */
     void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
 
+    /**
+     * Please dispatch through WindowStateResizeItem instead of directly calling this method from
+     * the system server.
+     */
     void resized(in ClientWindowFrames frames, boolean reportDraw,
             in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
             boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index c35b690..9f886c8 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -778,7 +778,8 @@
      * Each gamepad or joystick is given a unique, positive controller number when initially
      * configured by the system. This number may change due to events such as device disconnects /
      * reconnects or user initiated reassignment. Any change in number will trigger an event that
-     * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}.
+     * can be observed by registering an
+     * {@link android.hardware.input.InputManager.InputDeviceListener}.
      * </p>
      * <p>
      * All input devices which are not gamepads or joysticks will be assigned a controller number
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 1ec7c41..17a3a12 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -28,6 +28,7 @@
 import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
 import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
 import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.AnimationType;
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -469,8 +470,10 @@
             }
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
-            final boolean visible = mPendingFraction == 0 && source != null
-                    ? source.isVisible()
+            // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is animated from
+            // the hidden state.
+            final boolean visible = mPendingFraction == 0
+                    ? mAnimationType != ANIMATION_TYPE_SHOW
                     : !mFinished || mShownOnFinish;
 
             if (outState != null && source != null) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index fb24211..90663c7 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1283,7 +1283,6 @@
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if ((types & mTypesBeingCancelled) != 0) {
             final boolean monitoredAnimation =
                     animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
@@ -1295,12 +1294,15 @@
                     ImeTracker.forLatency().onHideCancelled(statsToken,
                             PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
                 }
+                ImeTracker.forLogging().onCancelled(statsToken,
+                        ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
             }
             throw new IllegalStateException("Cannot start a new insets animation of "
                     + Type.toString(types)
                     + " while an existing " + Type.toString(mTypesBeingCancelled)
                     + " is being cancelled.");
         }
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if (types == 0) {
             // nothing to animate.
             listener.onCancelled(null);
@@ -1309,8 +1311,6 @@
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
             return;
         }
-        ImeTracker.forLogging().onProgress(statsToken,
-                ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
         if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
         mLastStartedAnimTypes |= types;
 
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 99a7fe5..aad3bf2 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -20,11 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -42,15 +40,11 @@
 
 import com.android.internal.R;
 
-import dalvik.system.PathClassLoader;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Objects;
 
@@ -82,12 +76,6 @@
     private static final String TAG = LayoutInflater.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex";
-    /**
-     * Whether or not we use the precompiled layout.
-     */
-    private static final String USE_PRECOMPILED_LAYOUT = "view.precompiled_layout_enabled";
-
     /** Empty stack trace used to avoid log spam in re-throw exceptions. */
     private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
 
@@ -116,13 +104,6 @@
     private Factory2 mPrivateFactory;
     private Filter mFilter;
 
-    // Indicates whether we should try to inflate layouts using a precompiled layout instead of
-    // inflating from the XML resource.
-    private boolean mUseCompiledView;
-    // This variable holds the classloader that will be used to look for precompiled layouts. The
-    // The classloader includes the generated compiled_view.dex file.
-    private ClassLoader mPrecompiledClassLoader;
-
     /**
      * This is not a public API. Two APIs are now available to alleviate the need to access
      * this directly: {@link #createView(Context, String, String, AttributeSet)} and
@@ -259,7 +240,6 @@
     protected LayoutInflater(Context context) {
         StrictMode.assertConfigurationContext(context, "LayoutInflater");
         mContext = context;
-        initPrecompiledViews();
     }
 
     /**
@@ -277,7 +257,6 @@
         mFactory2 = original.mFactory2;
         mPrivateFactory = original.mPrivateFactory;
         setFilter(original.mFilter);
-        initPrecompiledViews();
     }
 
     /**
@@ -419,57 +398,6 @@
         }
     }
 
-    private void initPrecompiledViews() {
-        // Precompiled layouts are not supported in this release.
-        boolean enabled = false;
-        initPrecompiledViews(enabled);
-    }
-
-    private void initPrecompiledViews(boolean enablePrecompiledViews) {
-        mUseCompiledView = enablePrecompiledViews;
-
-        if (!mUseCompiledView) {
-            mPrecompiledClassLoader = null;
-            return;
-        }
-
-        // Make sure the application allows code generation
-        ApplicationInfo appInfo = mContext.getApplicationInfo();
-        if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
-            mUseCompiledView = false;
-            return;
-        }
-
-        // Try to load the precompiled layout file.
-        try {
-            mPrecompiledClassLoader = mContext.getClassLoader();
-            String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
-            if (new File(dexFile).exists()) {
-                mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
-            } else {
-                // If the precompiled layout file doesn't exist, then disable precompiled
-                // layouts.
-                mUseCompiledView = false;
-            }
-        } catch (Throwable e) {
-            if (DEBUG) {
-                Log.e(TAG, "Failed to initialized precompiled views layouts", e);
-            }
-            mUseCompiledView = false;
-        }
-        if (!mUseCompiledView) {
-            mPrecompiledClassLoader = null;
-        }
-    }
-
-    /**
-     * @hide for use by CTS tests
-     */
-    @TestApi
-    public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
-        initPrecompiledViews(enablePrecompiledLayouts);
-    }
-
     /**
      * Inflate a new view hierarchy from the specified xml resource. Throws
      * {@link InflateException} if there is an error.
@@ -529,10 +457,6 @@
                   + Integer.toHexString(resource) + ")");
         }
 
-        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
-        if (view != null) {
-            return view;
-        }
         XmlResourceParser parser = res.getLayout(resource);
         try {
             return inflate(parser, root, attachToRoot);
@@ -541,54 +465,6 @@
         }
     }
 
-    private @Nullable
-    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
-        boolean attachToRoot) {
-        if (!mUseCompiledView) {
-            return null;
-        }
-
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
-
-        // Try to inflate using a precompiled layout.
-        String pkg = res.getResourcePackageName(resource);
-        String layout = res.getResourceEntryName(resource);
-
-        try {
-            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
-            Method inflater = clazz.getMethod(layout, Context.class, int.class);
-            View view = (View) inflater.invoke(null, mContext, resource);
-
-            if (view != null && root != null) {
-                // We were able to use the precompiled inflater, but now we need to do some work to
-                // attach the view to the root correctly.
-                XmlResourceParser parser = res.getLayout(resource);
-                try {
-                    AttributeSet attrs = Xml.asAttributeSet(parser);
-                    advanceToRootNode(parser);
-                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
-
-                    if (attachToRoot) {
-                        root.addView(view, params);
-                    } else {
-                        view.setLayoutParams(params);
-                    }
-                } finally {
-                    parser.close();
-                }
-            }
-
-            return view;
-        } catch (Throwable e) {
-            if (DEBUG) {
-                Log.e(TAG, "Failed to use precompiled view", e);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-        }
-        return null;
-    }
-
     /**
      * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
      * found.
@@ -1050,7 +926,7 @@
      * of the general view creation logic, and thus may return {@code null} for some tags. This
      * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
      *
-     * @hide for use by precompiled layouts.
+     * @hide originally for internal use by precompiled layouts, which have since been removed.
      *
      * @param parent the parent view, used to inflate layout params
      * @param name the name of the XML tag used to define the view
@@ -1217,85 +1093,80 @@
                 + "reference. The layout ID " + value + " is not valid.");
         }
 
-        final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
-            (ViewGroup) parent, /*attachToRoot=*/true);
-        if (precompiled == null) {
-            final XmlResourceParser childParser = context.getResources().getLayout(layout);
+        final XmlResourceParser childParser = context.getResources().getLayout(layout);
+        try {
+            final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
 
-            try {
-                final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
-
-                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
-                    type != XmlPullParser.END_DOCUMENT) {
-                    // Empty.
-                }
-
-                if (type != XmlPullParser.START_TAG) {
-                    throw new InflateException(getParserStateDescription(context, childAttrs)
-                            + ": No start tag found!");
-                }
-
-                final String childName = childParser.getName();
-
-                if (TAG_MERGE.equals(childName)) {
-                    // The <merge> tag doesn't support android:theme, so
-                    // nothing special to do here.
-                    rInflate(childParser, parent, context, childAttrs, false);
-                } else {
-                    final View view = createViewFromTag(parent, childName,
-                        context, childAttrs, hasThemeOverride);
-                    final ViewGroup group = (ViewGroup) parent;
-
-                    final TypedArray a = context.obtainStyledAttributes(
-                        attrs, R.styleable.Include);
-                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
-                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
-                    a.recycle();
-
-                    // We try to load the layout params set in the <include /> tag.
-                    // If the parent can't generate layout params (ex. missing width
-                    // or height for the framework ViewGroups, though this is not
-                    // necessarily true of all ViewGroups) then we expect it to throw
-                    // a runtime exception.
-                    // We catch this exception and set localParams accordingly: true
-                    // means we successfully loaded layout params from the <include>
-                    // tag, false means we need to rely on the included layout params.
-                    ViewGroup.LayoutParams params = null;
-                    try {
-                        params = group.generateLayoutParams(attrs);
-                    } catch (RuntimeException e) {
-                        // Ignore, just fail over to child attrs.
-                    }
-                    if (params == null) {
-                        params = group.generateLayoutParams(childAttrs);
-                    }
-                    view.setLayoutParams(params);
-
-                    // Inflate all children.
-                    rInflateChildren(childParser, view, childAttrs, true);
-
-                    if (id != View.NO_ID) {
-                        view.setId(id);
-                    }
-
-                    switch (visibility) {
-                        case 0:
-                            view.setVisibility(View.VISIBLE);
-                            break;
-                        case 1:
-                            view.setVisibility(View.INVISIBLE);
-                            break;
-                        case 2:
-                            view.setVisibility(View.GONE);
-                            break;
-                    }
-
-                    group.addView(view);
-                }
-            } finally {
-                childParser.close();
+            while ((type = childParser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Empty.
             }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new InflateException(getParserStateDescription(context, childAttrs)
+                        + ": No start tag found!");
+            }
+
+            final String childName = childParser.getName();
+
+            if (TAG_MERGE.equals(childName)) {
+                // The <merge> tag doesn't support android:theme, so
+                // nothing special to do here.
+                rInflate(childParser, parent, context, childAttrs, false);
+            } else {
+                final View view =
+                        createViewFromTag(parent, childName, context, childAttrs, hasThemeOverride);
+                final ViewGroup group = (ViewGroup) parent;
+
+                final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Include);
+                final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
+                final int visibility = a.getInt(R.styleable.Include_visibility, -1);
+                a.recycle();
+
+                // We try to load the layout params set in the <include /> tag.
+                // If the parent can't generate layout params (ex. missing width
+                // or height for the framework ViewGroups, though this is not
+                // necessarily true of all ViewGroups) then we expect it to throw
+                // a runtime exception.
+                // We catch this exception and set localParams accordingly: true
+                // means we successfully loaded layout params from the <include>
+                // tag, false means we need to rely on the included layout params.
+                ViewGroup.LayoutParams params = null;
+                try {
+                    params = group.generateLayoutParams(attrs);
+                } catch (RuntimeException e) {
+                    // Ignore, just fail over to child attrs.
+                }
+                if (params == null) {
+                    params = group.generateLayoutParams(childAttrs);
+                }
+                view.setLayoutParams(params);
+
+                // Inflate all children.
+                rInflateChildren(childParser, view, childAttrs, true);
+
+                if (id != View.NO_ID) {
+                    view.setId(id);
+                }
+
+                switch (visibility) {
+                    case 0:
+                        view.setVisibility(View.VISIBLE);
+                        break;
+                    case 1:
+                        view.setVisibility(View.INVISIBLE);
+                        break;
+                    case 2:
+                        view.setVisibility(View.GONE);
+                        break;
+                }
+
+                group.addView(view);
+            }
+        } finally {
+            childParser.close();
         }
+
         LayoutInflater.consumeChildElements(parser);
     }
 
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 78716f5..8a44d4f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -43,7 +43,8 @@
  * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID
  * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}.
  * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an
- * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input
+ * {@link android.hardware.input.InputManager} instance
+ * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input
  * device IDs).
  *
  * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll
@@ -62,23 +63,37 @@
  * </ul>
  *
  * <b>Note</b> that not all valid input device source and motion axis inputs are necessarily
- * supported for scroll feedback. If you are implementing this interface, provide clear
- * documentation in your implementation class about which input device source and motion axis are
- * supported for your specific implementation. If you are using one of the implementations of this
- * interface, please refer to the documentation of the implementation for details on which input
- * device source and axis are supported.
+ * supported for scroll feedback; the implementation may choose to provide no feedback for some
+ * valid input device source and motion axis arguments.
  */
 @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
 public interface ScrollFeedbackProvider {
+
+    /**
+     * Creates a {@link ScrollFeedbackProvider} implementation for this device.
+     *
+     * <p>Use a feedback provider created by this method, unless you intend to use your custom
+     * scroll feedback providing logic. This allows your use cases to generate scroll feedback that
+     * is consistent with the rest of the use cases on the device.
+     *
+     * @param view the {@link View} for which to provide scroll feedback.
+     * @return the default {@link ScrollFeedbackProvider} implementation for the device.
+     */
+    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
+    @NonNull
+    static ScrollFeedbackProvider createProvider(@NonNull View view) {
+        return new HapticScrollFeedbackProvider(view);
+    }
+
     /**
      * Call this when the view has snapped to an item.
      *
-     *
      * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
      *          the snap.
      * @param source the input source of the motion causing the snap.
      * @param axis the axis of {@code event} that caused the item to snap.
      */
+    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
     void onSnapToItem(int inputDeviceId, int source, int axis);
 
     /**
@@ -99,6 +114,7 @@
      *                "start" for some views may be at the bottom of a scrolling list, while it may
      *                be at the top of scrolling list for others.
      */
+    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
     void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart);
 
     /**
@@ -122,5 +138,6 @@
      * @param axis the axis of {@code event} that caused scroll progress.
      * @param deltaInPixels the amount of scroll progress, in pixels.
      */
+    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
     void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels);
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 139c0be..e22207c 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -263,7 +264,7 @@
     private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,
             long nativeObject, int compatibility);
     private static native void nativeSetFrameRateCategory(
-            long transactionObj, long nativeObject, int category);
+            long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);
     private static native void nativeSetFrameRateSelectionStrategy(
             long transactionObj, long nativeObject, int strategy);
     private static native long nativeGetHandle(long nativeObject);
@@ -796,7 +797,7 @@
         if (nativeObject != 0) {
             // Only add valid surface controls to the registry. This is called at the end of this
             // method since its information is dumped if the process threshold is reached.
-            addToRegistry();
+            SurfaceControlRegistry.getProcessInstance().add(this);
         }
     }
 
@@ -1501,7 +1502,7 @@
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         } finally {
             super.finalize();
         }
@@ -1519,6 +1520,10 @@
      */
     public void release() {
         if (mNativeObject != 0) {
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "release", null, this, null);
+            }
             mFreeNativeResources.run();
             mNativeObject = 0;
             mNativeHandle = 0;
@@ -1532,7 +1537,7 @@
                     mChoreographer = null;
                 }
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         }
     }
 
@@ -1790,7 +1795,12 @@
         public float xDpi;
         public float yDpi;
 
-        public float refreshRate;
+        // Some modes have peak refresh rate lower than the panel vsync rate.
+        public float peakRefreshRate;
+        // Fixed rate of vsync deadlines for the panel.
+        // This can be higher then the peak refresh rate for some panel technologies
+        // See: VrrConfig.aidl
+        public float vsyncRate;
         public long appVsyncOffsetNanos;
         public long presentationDeadlineNanos;
         public int[] supportedHdrTypes;
@@ -1810,7 +1820,8 @@
                     + ", height=" + height
                     + ", xDpi=" + xDpi
                     + ", yDpi=" + yDpi
-                    + ", refreshRate=" + refreshRate
+                    + ", peakRefreshRate=" + peakRefreshRate
+                    + ", vsyncRate=" + vsyncRate
                     + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
                     + ", presentationDeadlineNanos=" + presentationDeadlineNanos
                     + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes)
@@ -1827,7 +1838,8 @@
                     && height == that.height
                     && Float.compare(that.xDpi, xDpi) == 0
                     && Float.compare(that.yDpi, yDpi) == 0
-                    && Float.compare(that.refreshRate, refreshRate) == 0
+                    && Float.compare(that.peakRefreshRate, peakRefreshRate) == 0
+                    && Float.compare(that.vsyncRate, vsyncRate) == 0
                     && appVsyncOffsetNanos == that.appVsyncOffsetNanos
                     && presentationDeadlineNanos == that.presentationDeadlineNanos
                     && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes)
@@ -1836,8 +1848,9 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
-                    presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes));
+            return Objects.hash(id, width, height, xDpi, yDpi, peakRefreshRate, vsyncRate,
+                    appVsyncOffsetNanos, presentationDeadlineNanos, group,
+                    Arrays.hashCode(supportedHdrTypes));
         }
     }
 
@@ -2757,8 +2770,10 @@
 
         private Transaction(long nativeObject) {
             mNativeObject = nativeObject;
-            mFreeNativeResources =
-                    sRegistry.registerNativeAllocation(this, mNativeObject);
+            mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+            if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) {
+                SurfaceControlRegistry.initializeCallStackDebugging();
+            }
         }
 
         private Transaction(Parcel in) {
@@ -2837,6 +2852,11 @@
             applyResizedSurfaces();
             notifyReparentedSurfaces();
             nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "apply", this, null, null);
+            }
         }
 
         /**
@@ -2912,6 +2932,10 @@
         @UnsupportedAppUsage
         public Transaction show(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "show", this, sc, null);
+            }
             nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
             return this;
         }
@@ -2926,6 +2950,10 @@
         @UnsupportedAppUsage
         public Transaction hide(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "hide", this, sc, null);
+            }
             nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
             return this;
         }
@@ -2942,6 +2970,10 @@
         @NonNull
         public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setPosition", this, sc, "x=" + x + " y=" + y);
+            }
             nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
             return this;
         }
@@ -2960,6 +2992,10 @@
             checkPreconditions(sc);
             Preconditions.checkArgument(scaleX >= 0, "Negative value passed in for scaleX");
             Preconditions.checkArgument(scaleY >= 0, "Negative value passed in for scaleY");
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setScale", this, sc, "sx=" + scaleX + " sy=" + scaleY);
+            }
             nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
             return this;
         }
@@ -2977,6 +3013,10 @@
         public Transaction setBufferSize(@NonNull SurfaceControl sc,
                 @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setBufferSize", this, sc, "w=" + w + " h=" + h);
+            }
             mResizedSurfaces.put(sc, new Point(w, h));
             return this;
         }
@@ -2997,6 +3037,10 @@
         public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
                        @Surface.Rotation int transformHint) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setFixedTransformHint", this, sc, "hint=" + transformHint);
+            }
             nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
             return this;
         }
@@ -3010,6 +3054,10 @@
         @NonNull
         public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetFixedTransformHint", this, sc, null);
+            }
             nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
             return this;
         }
@@ -3027,6 +3075,10 @@
         public Transaction setLayer(@NonNull SurfaceControl sc,
                 @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setLayer", this, sc, "z=" + z);
+            }
             nativeSetLayer(mNativeObject, sc.mNativeObject, z);
             return this;
         }
@@ -3036,6 +3088,10 @@
          */
         public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setRelativeLayer", this, sc, "relTo=" + relativeTo + " z=" + z);
+            }
             nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
             return this;
         }
@@ -3045,6 +3101,10 @@
          */
         public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetFixedTransformHint", this, sc, "region=" + transparentRegion);
+            }
             nativeSetTransparentRegionHint(mNativeObject,
                     sc.mNativeObject, transparentRegion);
             return this;
@@ -3061,6 +3121,10 @@
         public Transaction setAlpha(@NonNull SurfaceControl sc,
                 @FloatRange(from = 0.0, to = 1.0) float alpha) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setAlpha", this, sc, "alpha=" + alpha);
+            }
             nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
             return this;
         }
@@ -3116,6 +3180,11 @@
         public Transaction setMatrix(SurfaceControl sc,
                 float dsdx, float dtdx, float dtdy, float dsdy) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setMatrix", this, sc,
+                        "dsdx=" + dsdx + " dtdx=" + dtdx + " dtdy=" + dtdy + " dsdy=" + dsdy);
+            }
             nativeSetMatrix(mNativeObject, sc.mNativeObject,
                     dsdx, dtdx, dtdy, dsdy);
             return this;
@@ -3181,6 +3250,10 @@
         @UnsupportedAppUsage
         public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setWindowCrop", this, sc, "crop=" + crop);
+            }
             if (crop != null) {
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
                         crop.left, crop.top, crop.right, crop.bottom);
@@ -3203,6 +3276,10 @@
          */
         public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCrop", this, sc, "crop=" + crop);
+            }
             if (crop != null) {
                 Preconditions.checkArgument(crop.isValid(), "Crop isn't valid.");
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
@@ -3225,6 +3302,10 @@
          */
         public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCornerRadius", this, sc, "w=" + width + " h=" + height);
+            }
             nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
             return this;
         }
@@ -3239,6 +3320,10 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCornerRadius", this, sc, "cornerRadius=" + cornerRadius);
+            }
             nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
 
             return this;
@@ -3254,6 +3339,10 @@
          */
         public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setBackgroundBlurRadius", this, sc, "radius=" + radius);
+            }
             nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
             return this;
         }
@@ -3310,6 +3399,10 @@
         public Transaction reparent(@NonNull SurfaceControl sc,
                 @Nullable SurfaceControl newParent) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "reparent", this, sc, "newParent=" + newParent);
+            }
             long otherObject = 0;
             if (newParent != null) {
                 newParent.checkNotReleased();
@@ -3329,6 +3422,11 @@
         @UnsupportedAppUsage
         public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "reparent", this, sc,
+                        "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
+            }
             nativeSetColor(mNativeObject, sc.mNativeObject, color);
             return this;
         }
@@ -3339,6 +3437,10 @@
         */
         public Transaction unsetColor(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetColor", this, sc, null);
+            }
             nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
             return this;
         }
@@ -3350,6 +3452,10 @@
          */
         public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setSecure", this, sc, "secure=" + isSecure);
+            }
             if (isSecure) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
             } else {
@@ -3403,6 +3509,10 @@
         @NonNull
         public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setOpaque", this, sc, "opaque=" + isOpaque);
+            }
             if (isOpaque) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
             } else {
@@ -3572,6 +3682,10 @@
           */
         public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setShadowRadius", this, sc, "radius=" + shadowRadius);
+            }
             nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
             return this;
         }
@@ -3701,6 +3815,10 @@
          * @param sc The SurfaceControl to specify the frame rate category of.
          * @param category The frame rate category of this surface. The category value may influence
          * the system's choice of display frame rate.
+         * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not
+         * change if changing it would cause jank. Else {@code false}.
+         * This parameter is ignored when {@code category} is
+         * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.
          *
          * @return This transaction object.
          *
@@ -3709,10 +3827,10 @@
          * @hide
          */
         @NonNull
-        public Transaction setFrameRateCategory(
-                @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) {
+        public Transaction setFrameRateCategory(@NonNull SurfaceControl sc,
+                @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {
             checkPreconditions(sc);
-            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category);
+            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);
             return this;
         }
 
@@ -4258,8 +4376,7 @@
          * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
          * this API as well.
          * <p>
-         * @param sc         The {@link SurfaceControl} to set the
-         *                   {@link TrustedPresentationCallback} on
+         * @param sc         The {@link SurfaceControl} to set the callback on
          * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to
          *                   invoke the callback.
          * @param executor   The {@link Executor} where the callback will be invoked on.
@@ -4292,10 +4409,9 @@
         }
 
         /**
-         * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl}
+         * Clears the callback for a specific {@link SurfaceControl}
          *
-         * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be
-         *           cleared from
+         * @param sc The SurfaceControl that the callback should be cleared from
          * @return This transaction
          */
         @NonNull
@@ -4453,26 +4569,6 @@
         return -1;
     }
 
-    /**
-     * Adds this surface control to the registry for this process if it is created.
-     */
-    private void addToRegistry() {
-        final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
-        if (registry != null) {
-            registry.add(this);
-        }
-    }
-
-    /**
-     * Removes this surface control from the registry for this process.
-     */
-    private void removeFromRegistry() {
-        final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
-        if (registry != null) {
-            registry.remove(this);
-        }
-    }
-
     // Called by native
     private static void invokeReleaseCallback(Consumer<SyncFence> callback, long nativeFencePtr) {
         SyncFence fence = new SyncFence(nativeFencePtr);
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 67ac811..52be8f6 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.os.Build;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -78,6 +80,11 @@
             for (int i = 0; i < size; i++) {
                 final Map.Entry<SurfaceControl, Long> entry = entries.get(i);
                 final SurfaceControl sc = entry.getKey();
+                if (sc == null) {
+                    // Just skip if the key has since been removed from the weak hash map
+                    continue;
+                }
+
                 final long timeRegistered = entry.getValue();
                 pw.print("  ");
                 pw.print(sc.getName());
@@ -99,6 +106,9 @@
     // Number of surface controls to dump when the max threshold is exceeded
     private static final int DUMP_LIMIT = 256;
 
+    // An instance of a registry that is a no-op
+    private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry();
+
     // Static lock, must be held for all registry operations
     private static final Object sLock = new Object();
 
@@ -108,6 +118,22 @@
     // The registry for a given process
     private static volatile SurfaceControlRegistry sProcessRegistry;
 
+    // Whether call stack debugging has been initialized. This is evaluated only once per process
+    // instance when the first SurfaceControl.Transaction object is created
+    static boolean sCallStackDebuggingInitialized;
+
+    // Whether call stack debugging is currently enabled, ie. whether there is a valid match string
+    // for either a specific surface control name or surface control transaction method
+    static boolean sCallStackDebuggingEnabled;
+
+    // The name of the surface control to log stack traces for.  Always non-null if
+    // sCallStackDebuggingEnabled is true.  Can be combined with the match call.
+    private static String sCallStackDebuggingMatchName;
+
+    // The surface control transaction method name to log stack traces for.  Always non-null if
+    // sCallStackDebuggingEnabled is true.  Can be combined with the match name.
+    private static String sCallStackDebuggingMatchCall;
+
     // Mapping of the active SurfaceControls to the elapsed time when they were registered
     @GuardedBy("sLock")
     private final WeakHashMap<SurfaceControl, Long> mSurfaceControls;
@@ -155,6 +181,12 @@
         }
     }
 
+    @VisibleForTesting
+    public void setCallStackDebuggingParams(String matchName, String matchCall) {
+        sCallStackDebuggingMatchName = matchName.toLowerCase();
+        sCallStackDebuggingMatchCall = matchCall.toLowerCase();
+    }
+
     /**
      * Creates and initializes the registry for all SurfaceControls in this process. The caller must
      * hold the READ_FRAME_BUFFER permission.
@@ -191,11 +223,9 @@
      * createProcessInstance(Context) was previously called from a valid caller.
      * @hide
      */
-    @Nullable
-    @VisibleForTesting
     public static SurfaceControlRegistry getProcessInstance() {
         synchronized (sLock) {
-            return sProcessRegistry;
+            return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY;
         }
     }
 
@@ -243,6 +273,91 @@
     }
 
     /**
+     * Initializes global call stack debugging if this is a debug build and a filter is specified.
+     * This is a no-op if
+     *
+     * Usage:
+     *   adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset>
+     *   adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset>
+     *   adb reboot
+     */
+    final static void initializeCallStackDebugging() {
+        if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) {
+            // Return early if already initialized or this is not a debug build
+            return;
+        }
+
+        sCallStackDebuggingInitialized = true;
+        sCallStackDebuggingMatchCall =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+                        .toLowerCase();
+        sCallStackDebuggingMatchName =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+                        .toLowerCase();
+        // Only enable stack debugging if any of the match filters are set
+        sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty()
+                || !sCallStackDebuggingMatchName.isEmpty());
+        if (sCallStackDebuggingEnabled) {
+            Log.d(TAG, "Enabling transaction call stack debugging:"
+                    + " matchCall=" + sCallStackDebuggingMatchCall
+                    + " matchName=" + sCallStackDebuggingMatchName);
+        }
+    }
+
+    /**
+     * Dumps the callstack if it matches the global debug properties. Caller should first verify
+     * {@link #sCallStackDebuggingEnabled} is true.
+     *
+     * @param call the name of the call
+     * @param tx (optional) the transaction associated with this call
+     * @param sc the affected surface
+     * @param details additional details to print with the stack track
+     */
+    final void checkCallStackDebugging(@NonNull String call,
+            @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
+            @Nullable String details) {
+        if (!sCallStackDebuggingEnabled) {
+            return;
+        }
+        if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
+            return;
+        }
+        final String txMsg = tx != null ? "tx=" + tx.getId() + " ": "";
+        final String scMsg = sc != null ? " sc=" + sc.getName() + "": "";
+        final String msg = details != null
+                ? call + " (" + txMsg + scMsg + ") " + details
+                : call + " (" + txMsg + scMsg + ")";
+        Log.e(TAG, msg, new Throwable());
+    }
+
+    /**
+     * Tests whether the given surface control name/method call matches the filters set for the
+     * call stack debugging.
+     */
+    @VisibleForTesting
+    public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
+        final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
+        if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+            // Skip if target call doesn't match requested caller
+            return false;
+        }
+        final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
+        if (matchName && (name == null
+                || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+            // Skip if target surface doesn't match requested surface
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether call stack debugging is enabled for this process.
+     */
+    final static boolean isCallStackDebuggingEnabled() {
+        return sCallStackDebuggingEnabled;
+    }
+
+    /**
      * Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly
      * referenced surface controls.
      */
@@ -267,7 +382,27 @@
         synchronized (sLock) {
             if (sProcessRegistry != null) {
                 sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
+                pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized);
+                pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled);
+                pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName);
+                pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall);
             }
         }
     }
+
+    /**
+     * A no-op implementation of the registry.
+     */
+    private static class NoOpRegistry extends SurfaceControlRegistry {
+
+        @Override
+        public void setReportingThresholds(int maxLayersReportingThreshold,
+                int resetReportingThreshold, Reporter reporter) {}
+
+        @Override
+        void add(SurfaceControl sc) {}
+
+        @Override
+        void remove(SurfaceControl sc) {}
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cfde400..e9d0e4c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9308,6 +9308,7 @@
                 structure.setAutofillType(autofillType);
                 structure.setAutofillHints(getAutofillHints());
                 structure.setAutofillValue(getAutofillValue());
+                structure.setIsCredential(isCredential());
             }
             structure.setImportantForAutofill(getImportantForAutofill());
             structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes());
@@ -12161,12 +12162,6 @@
      * a precision touch gesture in a small area in either the X or Y dimension, such as
      * an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
      *
-     * <p>On Wear OS, these rects control where system-level swipe-to-dismiss gesture can start. If
-     * the attribute {@code android:windowSwipeToDismiss} has been set to {@code false}, the system
-     * will create an exclusion rect with size equal to the window frame size. In order words, the
-     * system swipe-to-dismiss will not apply, and the app must handle gestural input within itself.
-     * </p>
-     *
      * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the
      * exclusions it takes into account. The limit does not apply while the navigation
      * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 2cf5d5d..ec96167 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
@@ -1272,12 +1271,10 @@
      * @see InputDevice#getMotionRanges()
      * @see InputDevice#getMotionRange(int)
      * @see InputDevice#getMotionRange(int, int)
+     *
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
-    public boolean isHapticScrollFeedbackEnabled(
-            int inputDeviceId,
-            @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
-            int source) {
+    public boolean isHapticScrollFeedbackEnabled(int inputDeviceId, int axis, int source) {
         if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return false;
 
         if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
@@ -1318,12 +1315,10 @@
      *      returns {@code Integer.MAX_VALUE}.
      *
      * @see #isHapticScrollFeedbackEnabled(int, int, int)
+     *
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
-    public int getHapticScrollFeedbackTickInterval(
-            int inputDeviceId,
-            @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
-            int source) {
+    public int getHapticScrollFeedbackTickInterval(int inputDeviceId, int axis, int source) {
         if (!mRotaryEncoderHapticScrollFeedbackEnabled) {
             return NO_HAPTIC_SCROLL_TICK_INTERVAL;
         }
@@ -1343,9 +1338,6 @@
      * Checks if the View-based haptic scroll feedback implementation is enabled for
      * {@link InputDevice#SOURCE_ROTARY_ENCODER}s.
      *
-     * <p>If this method returns {@code true}, the {@link HapticScrollFeedbackProvider} will be
-     * muted for rotary encoders in favor of View's scroll haptics implementation.
-     *
      * @hide
      */
     public boolean isViewBasedRotaryEncoderHapticScrollFeedbackEnabled() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index aa47f07..4da02f9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -655,6 +655,10 @@
      */
     private boolean mCheckIfCanDraw = false;
 
+    private boolean mWasLastDrawCanceled;
+    private boolean mLastTraversalWasVisible = true;
+    private boolean mLastDrawScreenOff;
+
     private boolean mDrewOnceForSync = false;
 
     int mSyncSeqId = 0;
@@ -1549,7 +1553,8 @@
                         mHandler,
                         DisplayManager.EVENT_FLAG_DISPLAY_ADDED
                         | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+                        mBasePackageName);
     }
 
     /**
@@ -1730,7 +1735,7 @@
                         attrs.getTitle().toString());
                 mAttachInfo.mThreadedRenderer = renderer;
                 renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
-                updateColorModeIfNeeded(attrs.getColorMode());
+                updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom());
                 updateRenderHdrSdrRatio();
                 updateForceDarkMode();
                 mAttachInfo.mHardwareAccelerated = true;
@@ -1889,12 +1894,19 @@
     }
 
     void handleAppVisibility(boolean visible) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
+                    "%s visibilityChanged oldVisibility=%b newVisibility=%b", mTag,
+                    mAppVisible, visible));
+        }
         if (mAppVisible != visible) {
             final boolean previousVisible = getHostVisibility() == View.VISIBLE;
             mAppVisible = visible;
             final boolean currentVisible = getHostVisibility() == View.VISIBLE;
             // Root view only cares about whether it is visible or not.
             if (previousVisible != currentVisible) {
+                Log.d(mTag, "visibilityChanged oldVisibility=" + previousVisible + " newVisibility="
+                        + currentVisible);
                 mAppVisibilityChanged = true;
                 scheduleTraversals();
             }
@@ -2002,6 +2014,10 @@
                     Slog.i(mTag, "DisplayState - old: " + oldDisplayState
                             + ", new: " + newDisplayState);
                 }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER,
+                            "vri#screenState[" + mTag + "] state=", newDisplayState);
+                }
                 if (oldDisplayState != newDisplayState) {
                     mAttachInfo.mDisplayState = newDisplayState;
                     pokeDrawLockIfNeeded();
@@ -2232,9 +2248,7 @@
             mStopped = stopped;
             final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
             if (renderer != null) {
-                if (DEBUG_DRAW) {
-                    Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
-                }
+                if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
                 renderer.setStopped(mStopped);
             }
             if (!mStopped) {
@@ -3253,8 +3267,8 @@
                 || mForceNextWindowRelayout) {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        TextUtils.formatSimple("relayoutWindow#"
-                                        + "first=%b/resize=%b/vis=%b/params=%b/force=%b",
+                        TextUtils.formatSimple("%s-relayoutWindow#"
+                                        + "first=%b/resize=%b/vis=%b/params=%b/force=%b", mTag,
                                 mFirst, windowShouldResize, viewVisibilityChanged, params != null,
                                 mForceNextWindowRelayout));
             }
@@ -3351,7 +3365,7 @@
                 }
                 final boolean alwaysConsumeSystemBarsChanged =
                         mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
-                updateColorModeIfNeeded(lp.getColorMode());
+                updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom());
                 surfaceCreated = !hadSurface && mSurface.isValid();
                 surfaceDestroyed = hadSurface && !mSurface.isValid();
 
@@ -3843,11 +3857,7 @@
         boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
         boolean cancelAndRedraw = cancelDueToPreDrawListener
                  || (cancelDraw && mDrewOnceForSync);
-        if (cancelAndRedraw) {
-            Log.d(mTag, "Cancelling draw."
-                    + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
-                    + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
-        }
+
         if (!cancelAndRedraw) {
             // A sync was already requested before the WMS requested sync. This means we need to
             // sync the buffer, regardless if WMS wants to sync the buffer.
@@ -3871,6 +3881,9 @@
         }
 
         if (!isViewVisible) {
+            if (mLastTraversalWasVisible) {
+                logAndTrace("Not drawing due to not visible");
+            }
             mLastPerformTraversalsSkipDrawReason = "view_not_visible";
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
@@ -3879,15 +3892,26 @@
                 mPendingTransitions.clear();
             }
 
-            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
-                    "view not visible");
+            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
+                    mPendingTransaction, "view not visible");
         } else if (cancelAndRedraw) {
+            if (!mWasLastDrawCanceled) {
+                logAndTrace("Canceling draw."
+                        + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+                        + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
+            }
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
                 ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
                 : "cancel_" + cancelReason;
             // Try again
             scheduleTraversals();
         } else {
+            if (mWasLastDrawCanceled) {
+                logAndTrace("Draw frame after cancel");
+            }
+            if (!mLastTraversalWasVisible) {
+                logAndTrace("Start draw after previous draw not visible");
+            }
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
                     mPendingTransitions.get(i).startChangingAnimations();
@@ -3895,10 +3919,12 @@
                 mPendingTransitions.clear();
             }
             if (!performDraw(mActiveSurfaceSyncGroup)) {
-                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
-                        mLastPerformDrawSkippedReason);
+                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
+                        mPendingTransaction, mLastPerformDrawSkippedReason);
             }
         }
+        mWasLastDrawCanceled = cancelAndRedraw;
+        mLastTraversalWasVisible = isViewVisible;
 
         if (mAttachInfo.mContentCaptureEvents != null) {
             notifyContentCaptureEvents();
@@ -4688,10 +4714,7 @@
 
                 return didProduceBuffer -> {
                     if (!didProduceBuffer) {
-                        Trace.instant(Trace.TRACE_TAG_VIEW,
-                                "Transaction not synced due to no frame drawn-" + mTag);
-                        Log.d(mTag, "Pending transaction will not be applied in sync with a draw "
-                                + "because there was nothing new to draw");
+                        logAndTrace("Transaction not synced due to no frame drawn");
                         mBlastBufferQueue.applyPendingTransactions(frame);
                     }
                 };
@@ -4708,17 +4731,26 @@
         mLastPerformDrawSkippedReason = null;
         if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
             mLastPerformDrawSkippedReason = "screen_off";
+            if (!mLastDrawScreenOff) {
+                logAndTrace("Not drawing due to screen off");
+            }
+            mLastDrawScreenOff = true;
             return false;
         } else if (mView == null) {
             mLastPerformDrawSkippedReason = "no_root_view";
             return false;
         }
 
+        if (mLastDrawScreenOff) {
+            logAndTrace("Resumed drawing after screen turned on");
+            mLastDrawScreenOff = false;
+        }
+
         final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw-" + mTag);
 
         addFrameCommitCallbackIfNeeded();
 
@@ -4774,8 +4806,8 @@
             if (mSurfaceHolder != null && mSurface.isValid()) {
                 usingAsyncReport = true;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
-                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
-                            "SurfaceHolder");
+                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null,
+                            pendingTransaction, "SurfaceHolder");
                 });
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4789,8 +4821,8 @@
         }
 
         if (!usingAsyncReport) {
-            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
-                    "no async report");
+            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null,
+                    pendingTransaction, "no async report");
         }
 
         if (mPerformContentCapture) {
@@ -4800,13 +4832,14 @@
     }
 
     private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup,
-            @Nullable Transaction pendingTransaction, String logReason) {
+            boolean hasPendingTransaction, @Nullable Transaction pendingTransaction,
+            String logReason) {
         if (surfaceSyncGroup != null) {
-            if (pendingTransaction != null) {
+            if (hasPendingTransaction && pendingTransaction != null) {
                 surfaceSyncGroup.addTransaction(pendingTransaction);
             }
             surfaceSyncGroup.markSyncReady();
-        } else if (pendingTransaction != null) {
+        } else if (hasPendingTransaction && pendingTransaction != null) {
             Trace.instant(Trace.TRACE_TAG_VIEW,
                     "Transaction not synced due to " + logReason + "-" + mTag);
             if (DEBUG_BLAST) {
@@ -5653,7 +5686,8 @@
         mUpdateHdrSdrRatioInfo = true;
     }
 
-    private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) {
+    private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode,
+            float desiredRatio) {
         if (mAttachInfo.mThreadedRenderer == null) {
             return;
         }
@@ -5667,7 +5701,10 @@
                 && !getConfiguration().isScreenWideColorGamut()) {
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
         }
-        float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+        float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+        if (desiredRatio == 0 || desiredRatio > automaticRatio) {
+            desiredRatio = automaticRatio;
+        }
         if (desiredRatio != mDesiredHdrSdrRatio) {
             mDesiredHdrSdrRatio = desiredRatio;
             updateRenderHdrSdrRatio();
@@ -8606,10 +8643,6 @@
             mLastLayoutFrame.set(frame);
         }
 
-        if (mOnBackInvokedDispatcher.isSystemGestureExclusionNeeded()) {
-            setRootSystemGestureExclusionRects(List.of(frame));
-        }
-
         final WindowConfiguration winConfig = getCompatWindowConfiguration();
         mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
                 ? winConfig.getMaxBounds()
@@ -9054,8 +9087,8 @@
             mAdded = false;
             AnimationHandler.removeRequestor(this);
         }
-        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
-                "shutting down VRI");
+        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
+                mPendingTransaction, "shutting down VRI");
         WindowManagerGlobal.getInstance().doRemoveView(this);
     }
 
@@ -10548,6 +10581,8 @@
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
                 boolean dragResizing) {
+            // Although this is a AIDL method, it will only be triggered in local process through
+            // either WindowStateResizeItem or WindowlessWindowManager.
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
                 viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
@@ -11424,8 +11459,7 @@
     @Override
     public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) {
         if (mRemoved || !isHardwareEnabled()) {
-            Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw applyImmediately-" + mTag);
-            Log.d(mTag, "applyTransactionOnDraw: Applying transaction immediately");
+            logAndTrace("applyTransactionOnDraw applyImmediately");
             t.apply();
         } else {
             Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw-" + mTag);
@@ -11439,7 +11473,11 @@
 
     @Override
     public @SurfaceControl.BufferTransform int getBufferTransformHint() {
-        return mSurfaceControl.getTransformHint();
+        if (mSurfaceControl.isValid()) {
+            return mSurfaceControl.getTransformHint();
+        } else {
+            return SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+        }
     }
 
     @Override
@@ -11574,7 +11612,14 @@
             Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer);
         }
 
-        surfaceSyncGroup.addTransaction(mPendingTransaction);
+        final Transaction t;
+        if (mHasPendingTransactions) {
+            t = new Transaction();
+            t.merge(mPendingTransaction);
+        } else {
+            t = null;
+        }
+
         mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
             @Override
             public void onFrameDraw(long frame) {
@@ -11587,6 +11632,9 @@
                             "Received frameDrawingCallback syncResult=" + syncResult + " frameNum="
                                     + frame + ".");
                 }
+                if (t != null) {
+                    mergeWithNextTransaction(t, frame);
+                }
 
                 // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
                 // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
@@ -11808,4 +11856,11 @@
             @NonNull Consumer<Boolean> listener) {
         t.clearTrustedPresentationCallback(getSurfaceControl());
     }
+
+    private void logAndTrace(String msg) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
+        }
+        Log.d(mTag, msg);
+    }
 }
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 2c2ae06..bb2c7c8 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -397,6 +397,13 @@
     public void setImportantForAutofill(@AutofillImportance int mode) {}
 
     /**
+     * Sets whether the node is a credential. See {@link View#isCredential}.
+     *
+     * @hide
+     */
+    public void setIsCredential(boolean isCredential) {}
+
+    /**
      * Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}.
      *
      * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2f04b0c..87537fbc 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -24,6 +24,8 @@
 
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -1330,6 +1332,47 @@
     }
 
     /**
+     * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+     * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
+     * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
+     *
+     * <p>By default the system will choose an amount of HDR headroom that is appropriate
+     * for the underlying device capabilities & bit-depth of the panel. However, for some types
+     * of content this can end up being more headroom than necessary or desired. An example
+     * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired
+     * without overly influencing the perceived brightness of the majority SDR content. This can
+     * also be used to animate in/out of an HDR range for smoother transitions.</p>
+     *
+     * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety
+     * of factors such as ambient conditions, display capabilities, or bit-depth limitations.
+     * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the
+     * current value.</p>
+     *
+     * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR)
+     *                        and <= 10,000.0. Passing 0.0 will reset to the default, automatically
+     *                        chosen value.
+     * @see #getDesiredHdrHeadroom()
+     * @see Display#getHdrSdrRatio()
+     */
+    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+    public void setDesiredHdrHeadroom(
+            @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.setDesiredHdrHeadroom(desiredHeadroom);
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)}
+     * @return The amount of HDR headroom set, or 0 for automatic/default behavior.
+     * @see #setDesiredHdrHeadroom(float)
+     */
+    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+    public float getDesiredHdrHeadroom() {
+        return getAttributes().getDesiredHdrHeadroom();
+    }
+
+    /**
      * If {@code isPreferred} is true, this method requests that the connected display does minimal
      * post processing when this window is visible on the screen. Otherwise, it requests that the
      * display switches back to standard image processing.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2f4bea0..4f03ce9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST;
 import static android.view.View.STATUS_BAR_DISABLE_BACK;
 import static android.view.View.STATUS_BAR_DISABLE_CLOCK;
 import static android.view.View.STATUS_BAR_DISABLE_EXPAND;
@@ -82,6 +83,8 @@
 
 import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -1683,7 +1686,7 @@
      * orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This
      * listener gives application an opportunity to selectively react to device orientation changes.
      * The newly added listener will be called with current proposed rotation. Note that the context
-     * of this window manager instance must be a {@link android.annotation.UiContext}.
+     * of this window manager instance must be a {@code UiContext}.
      *
      * @param executor The executor on which callback method will be invoked.
      * @param listener Called when the proposed rotation for the context is being delivered.
@@ -1691,7 +1694,7 @@
      *                 {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and
      *                 {@link Surface#ROTATION_270}.
      * @throws UnsupportedOperationException if this method is called on an instance that is not
-     *         associated with a {@link android.annotation.UiContext}.
+     *         associated with a {@code UiContext}.
      */
     default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull IntConsumer listener) {
@@ -3113,7 +3116,7 @@
         /**
          * Never animate position changes of the window.
          *
-         * @see android.R.attr#Window_windowNoMoveAnimation
+         * @see android.R.styleable#Window_windowNoMoveAnimation
          * {@hide}
          */
         @UnsupportedAppUsage
@@ -3903,6 +3906,7 @@
          * This value is ignored if {@link #preferredDisplayModeId} is set.
          * @hide
          */
+        @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
         @TestApi
         public float preferredMinDisplayRefreshRate;
 
@@ -3912,6 +3916,7 @@
          * This value is ignored if {@link #preferredDisplayModeId} is set.
          * @hide
          */
+        @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
         @TestApi
         public float preferredMaxDisplayRefreshRate;
 
@@ -4314,6 +4319,9 @@
         @ActivityInfo.ColorMode
         private int mColorMode = COLOR_MODE_DEFAULT;
 
+        /** @hide */
+        private float mDesiredHdrHeadroom = 0;
+
         /**
          * Carries the requests about {@link WindowInsetsController.Appearance} and
          * {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
@@ -4526,7 +4534,7 @@
          * Set whether animations can be played for position changes on this window. If disabled,
          * the window will move to its new position instantly without animating.
          *
-         * @attr ref android.R.attr#Window_windowNoMoveAnimation
+         * @attr ref android.R.styleable#Window_windowNoMoveAnimation
          */
         public void setCanPlayMoveAnimation(boolean enable) {
             if (enable) {
@@ -4541,7 +4549,7 @@
          * This does not guarantee that an animation will be played in all such situations. For
          * example, drag-resizing may move the window but not play an animation.
          *
-         * @attr ref android.R.attr#Window_windowNoMoveAnimation
+         * @attr ref android.R.styleable#Window_windowNoMoveAnimation
          */
         public boolean canPlayMoveAnimation() {
             return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0;
@@ -4716,6 +4724,39 @@
         }
 
         /**
+         * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+         * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
+         * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
+         *
+         * @see Window#setDesiredHdrHeadroom(float)
+         * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR)
+         *                        to 10,000.0, or 0.0 to reset to default.
+         */
+        @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+        public void setDesiredHdrHeadroom(
+                @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) {
+            if (!Float.isFinite(desiredHeadroom)) {
+                throw new IllegalArgumentException("desiredHeadroom must be finite: "
+                        + desiredHeadroom);
+            }
+            if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) {
+                throw new IllegalArgumentException(
+                        "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: "
+                                + desiredHeadroom);
+            }
+            mDesiredHdrHeadroom = desiredHeadroom;
+        }
+
+        /**
+         * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)}
+         * @return The amount of HDR headroom set, or 0 for automatic/default behavior.
+         */
+        @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+        public float getDesiredHdrHeadroom() {
+            return mDesiredHdrHeadroom;
+        }
+
+        /**
          * <p>
          * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
          * but instead of dimmed, the content behind the window will be blurred (or combined with
@@ -4865,6 +4906,7 @@
             checkNonRecursiveParams();
             out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
             out.writeInt(mDisplayFlags);
+            out.writeFloat(mDesiredHdrHeadroom);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4936,6 +4978,7 @@
             forciblyShownTypes = in.readInt();
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
             mDisplayFlags = in.readInt();
+            mDesiredHdrHeadroom = in.readFloat();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5196,6 +5239,11 @@
                 changes |= COLOR_MODE_CHANGED;
             }
 
+            if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) {
+                mDesiredHdrHeadroom = o.mDesiredHdrHeadroom;
+                changes |= COLOR_MODE_CHANGED;
+            }
+
             if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) {
                 preferMinimalPostProcessing = o.preferMinimalPostProcessing;
                 changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
@@ -5423,6 +5471,9 @@
             if (mColorMode != COLOR_MODE_DEFAULT) {
                 sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
             }
+            if (mDesiredHdrHeadroom != 0) {
+                sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom);
+            }
             if (preferMinimalPostProcessing) {
                 sb.append(" preferMinimalPostProcessing=");
                 sb.append(preferMinimalPostProcessing);
@@ -5821,6 +5872,7 @@
      *
      * @hide
      */
+    @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR")
     @TestApi
     @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
     default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) {
@@ -5836,6 +5888,7 @@
      *
      * @hide
      */
+    @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR")
     @TestApi
     @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
     default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 7ad43c7..26298bc 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -47,7 +47,6 @@
  * @see WindowInsets#getInsets(int)
  * @see WindowManager#getCurrentWindowMetrics()
  * @see WindowManager#getMaximumWindowMetrics()
- * @see android.annotation.UiContext
  */
 public final class WindowMetrics {
     @NonNull
@@ -99,8 +98,7 @@
     }
 
     /**
-     * Returns the bounds of the area associated with this window or
-     * {@link android.annotation.UiContext}.
+     * Returns the bounds of the area associated with this window or {@code UiContext}.
      * <p>
      * <b>Note that the size of the reported bounds can have different size than
      * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
@@ -133,7 +131,7 @@
 
     /**
      * Returns the {@link WindowInsets} of the area associated with this window or
-     * {@link android.annotation.UiContext}.
+     * {@code UiContext}.
      *
      * @return the {@link WindowInsets} of the visual area.
      */
@@ -146,9 +144,8 @@
     }
 
     /**
-     * Returns the density of the area associated with this window or
-     * {@link android.annotation.UiContext}, which uses the same units as
-     * {@link android.util.DisplayMetrics#density}.
+     * Returns the density of the area associated with this window or {@code UiContext},
+     * which uses the same units as {@link android.util.DisplayMetrics#density}.
      *
      * @see android.util.DisplayMetrics#DENSITY_DEFAULT
      * @see android.util.DisplayMetrics#density
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 60f46e6..12ce0f4 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -1731,6 +1731,9 @@
     @Override
     public void sendAttachOverlayResult(
             @AccessibilityService.AttachOverlayResult int result, int interactionId) {
+        if (!Flags.a11yOverlayCallbacks()) {
+            return;
+        }
         synchronized (mInstanceLock) {
             if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
                 final Pair<Executor, IntConsumer> pair =
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e6a8b78..43bfe13 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1562,9 +1562,8 @@
      * describes the action.
      * </p>
      * <p>
-     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
-     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
-     * view.
+     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence,
+     * AccessibilityViewCommand)} to register an action directly on the view.
      * <p>
      *   <strong>Note:</strong> Cannot be called from an
      *   {@link android.accessibilityservice.AccessibilityService}.
@@ -5167,8 +5166,7 @@
      * </p>
      * <aside class="note">
      * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
-     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
-     * view.
+     * CharSequence, AccessibilityViewCommand)} to register an action directly on the view.
      * </p>
      */
     public static final class AccessibilityAction implements Parcelable {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 09b2f9c..fa0052c 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -99,7 +99,6 @@
     /** @hide */
     public static final int UNDEFINED_CONNECTION_ID = -1;
     /** @hide */
-    @TestApi
     public static final int UNDEFINED_WINDOW_ID = -1;
     /** @hide */
     public static final int ANY_WINDOW_ID = -2;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e31ad82..6888b50 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -1,8 +1,24 @@
 package: "android.view.accessibility"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
+flag {
+    name: "a11y_overlay_callbacks"
+    namespace: "accessibility"
+    description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+    bug: "304478691"
+}
+
+flag {
+    namespace: "accessibility"
+    name: "allow_shortcut_chooser_on_lockscreen"
+    description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
+    bug: "303871725"
+}
+
 flag {
     namespace: "accessibility"
     name: "force_invert_color"
     description: "Enable force force-dark for smart inversion and dark theme everywhere"
     bug: "282821643"
-}
\ No newline at end of file
+}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index a07b62f..b3359b7 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,8 +16,8 @@
 
 package android.view.animation;
 
-import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
-import static android.view.flags.Flags.expectedPresentationTimeApi;
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY;
+import static android.view.flags.Flags.expectedPresentationTimeReadOnly;
 
 import android.annotation.AnimRes;
 import android.annotation.FlaggedApi;
@@ -56,7 +56,7 @@
     private static final int SEQUENTIALLY = 1;
 
      /**
-     * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
+     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
      * this change ID enables to use expectedPresentationTime instead of the frameTime
      * for the frame start time .
      *
@@ -67,6 +67,11 @@
     @Overridable
     public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
 
+    private static boolean sExpectedPresentationTimeFlagValue;
+    static {
+        sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
+    }
+
     private static class AnimationState {
         boolean animationClockLocked;
         long currentVsyncTimeMillis;
@@ -108,11 +113,14 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
     public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) {
         AnimationState state = sAnimationState.get();
         state.animationClockLocked = true;
         state.currentVsyncTimeMillis = vsyncMillis;
-        state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
+        if (!sExpectedPresentationTimeFlagValue) {
+            state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
+        }
     }
 
     /**
@@ -155,9 +163,9 @@
      * @return the expected presentation time of a frame in the
      *         {@link System#nanoTime()} time base.
      */
-    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
     public static long getExpectedPresentationTimeNanos() {
-        if (!expectedPresentationTimeApi()) {
+        if (!sExpectedPresentationTimeFlagValue) {
             return SystemClock.uptimeMillis();
         }
 
@@ -173,7 +181,7 @@
      * @return the expected presentation time of a frame in the
      *         {@link SystemClock#uptimeMillis()} time base.
      */
-    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
     public static long getExpectedPresentationTimeMillis() {
         return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
     }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 34e4c37..96574f5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,7 +16,6 @@
 
 package android.view.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -31,12 +30,10 @@
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
-import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -53,19 +50,15 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.Flags;
 import android.service.autofill.IFillCallback;
@@ -89,7 +82,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.CheckBox;
 import android.widget.DatePicker;
@@ -119,7 +111,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import sun.misc.Cleaner;
@@ -189,12 +180,6 @@
  * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
- * <p>There is another choice for the application to provide it's datasets to the Autofill framework
- * by setting an {@link AutofillRequestCallback} through
- * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
- * its callback instead of the default {@link AutofillService}. See
- * {@link AutofillRequestCallback} for more details.
- *
  * <h3 id="additional-notes">Additional notes</h3>
  *
  * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -334,7 +319,6 @@
     /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
     /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
-    /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
 
     // NOTE: flag below is used by the session start receiver only, hence it can have values above
     /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -676,11 +660,6 @@
     @GuardedBy("mLock")
     private boolean mEnabledForAugmentedAutofillOnly;
 
-    @GuardedBy("mLock")
-    @Nullable private AutofillRequestCallback mAutofillRequestCallback;
-    @GuardedBy("mLock")
-    @Nullable private Executor mRequestCallbackExecutor;
-
     private boolean mScreenHasCredmanField;
 
     /**
@@ -2434,38 +2413,6 @@
         return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
-    /**
-     * Sets the client's suggestions callback for autofill.
-     *
-     * @see AutofillRequestCallback
-     *
-     * @param executor specifies the thread upon which the callbacks will be invoked.
-     * @param callback which handles autofill request to provide client's suggestions.
-     */
-    @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-    public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull AutofillRequestCallback callback) {
-        if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!");
-        }
-
-        synchronized (mLock) {
-            mRequestCallbackExecutor = executor;
-            mAutofillRequestCallback = callback;
-        }
-    }
-
-    /**
-     * clears the client's suggestions callback for autofill.
-     */
-    public void clearAutofillRequestCallback() {
-        synchronized (mLock) {
-            mRequestCallbackExecutor = null;
-            mAutofillRequestCallback = null;
-        }
-    }
-
     @GuardedBy("mLock")
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
             @NonNull AutofillValue value, int flags) {
@@ -2526,13 +2473,6 @@
                 }
             }
 
-            if (mAutofillRequestCallback != null) {
-                if (sDebug) {
-                    Log.d(TAG, "startSession with the client suggestions provider");
-                }
-                flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
-            }
-
             mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, clientActivity,
@@ -2890,28 +2830,6 @@
         }
     }
 
-    private void onFillRequest(InlineSuggestionsRequest request,
-            CancellationSignal cancellationSignal, FillCallback callback) {
-        final AutofillRequestCallback autofillRequestCallback;
-        final Executor executor;
-        synchronized (mLock) {
-            autofillRequestCallback = mAutofillRequestCallback;
-            executor = mRequestCallbackExecutor;
-        }
-        if (autofillRequestCallback != null && executor != null) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() ->
-                        autofillRequestCallback.onFillRequest(
-                                request, cancellationSignal, callback));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        } else {
-            callback.onSuccess(null);
-        }
-    }
-
     /** @hide */
     public static final int SET_STATE_FLAG_ENABLED = 0x01;
     /** @hide */
@@ -3474,7 +3392,7 @@
             return false;
         }
         for (String hint : hints) {
-            if (Objects.equals(hint, View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+            if (hint != null && hint.startsWith(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
                 return true;
             }
         }
@@ -4495,23 +4413,6 @@
         }
 
         @Override
-        public void requestFillFromClient(int id, InlineSuggestionsRequest request,
-                IFillCallback callback) {
-            final AutofillManager afm = mAfm.get();
-            if (afm != null) {
-                ICancellationSignal transport = CancellationSignal.createTransport();
-                try {
-                    callback.onCancellable(transport);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error requesting a cancellation", e);
-                }
-
-                afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
-                        new FillCallback(callback, id));
-            }
-        }
-
-        @Override
         public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
deleted file mode 100644
index e632a58..0000000
--- a/core/java/android/view/autofill/AutofillRequestCallback.java
+++ /dev/null
@@ -1,72 +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 android.view.autofill;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.CancellationSignal;
-import android.service.autofill.FillCallback;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-/**
- * <p>This class is used to provide some input suggestions to the Autofill framework.
- *
- * <P>When the user is requested to input something, Autofill will try to query input suggestions
- * for the user choosing. If the application want to provide some internal input suggestions,
- * implements this callback and register via
- * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
- * AutofillRequestCallback)}. Autofill will callback the
- * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
- * input suggestions.
- *
- * <P>To make sure the callback to take effect, must register before the autofill session starts.
- * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
- * session, and then the callback will be used at the next restarted session.
- *
- * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
- * {@link AutofillId}s from its view structure. Below is an example:
- * <pre class="prettyprint">
- * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
- * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
- * </pre>
- * To learn more about creating a {@link android.service.autofill.FillResponse}, read
- * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
- *
- * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
- * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
- * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
- * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
- * client would like to keep no suggestions for the field, respond with an empty
- * {@link android.service.autofill.FillResponse} which has no dataset.
- *
- * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
- * the keyboard may choose to block your app from the inline strip.
- */
-public interface AutofillRequestCallback {
-    /**
-     * Called by the Android system to decide if a screen can be autofilled by the callback.
-     *
-     * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
-     *     currently inline suggestions are supported and can be displayed.
-     * @param cancellationSignal signal for observing cancellation requests. The system will use
-     *     this to notify you that the fill result is no longer needed and you should stop
-     *     handling this fill request in order to save resources.
-     * @param callback object used to notify the result of the request.
-     */
-    void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
-            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
-}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 6e13097..917a974 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,11 +24,9 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.service.autofill.IFillCallback;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.KeyEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -149,12 +147,6 @@
    void requestShowSoftInput(in AutofillId id);
 
     /**
-     * Requests to determine if a screen can be autofilled by the client app.
-     */
-    void requestFillFromClient(int id, in InlineSuggestionsRequest request,
-            in IFillCallback callback);
-
-    /**
      * Notifies autofill ids that require to show the fill dialog.
      */
     void notifyFillDialogTriggerIds(in List<AutofillId> ids);
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 2c7d326..57011e8 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -60,7 +60,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 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.Set;
@@ -362,14 +364,6 @@
             "enable_content_protection_receiver";
 
     /**
-     * Sets the size of the app blocklist for the content protection flow.
-     *
-     * @hide
-     */
-    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
-            "content_protection_apps_blocklist_size";
-
-    /**
      * Sets the size of the in-memory ring buffer for the content protection flow.
      *
      * @hide
@@ -377,6 +371,30 @@
     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
             "content_protection_buffer_size";
 
+    /**
+     * Sets the config for content protection required groups.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG =
+            "content_protection_required_groups_config";
+
+    /**
+     * Sets the config for content protection optional groups.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG =
+            "content_protection_optional_groups_config";
+
+    /**
+     * Sets the threshold for content protection optional groups.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD =
+            "content_protection_optional_groups_threshold";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -414,9 +432,19 @@
     /** @hide */
     public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
     /** @hide */
-    public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
-    /** @hide */
     public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
+    /** @hide */
+    public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
+            Collections.emptyList();
+    /** @hide */
+    public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = "";
+    /** @hide */
+    public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS =
+            Collections.emptyList();
+    /** @hide */
+    public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = "";
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0;
 
     private final Object mLock = new Object();
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index dc3d323..bb815c0 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -183,7 +183,8 @@
     public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
 
     /**
-     * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
+     * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
      * the virtual children with a pair of view tree appearing and view tree appeared events.
      */
     @ChangeId
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2241fd5..b44d6a4 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -317,16 +317,14 @@
             }
         }
 
-        // Should not be possible for mComponentName to be null here but check anyway
-        if (mManager.mOptions.contentProtectionOptions.enableReceiver
-                && mManager.getContentProtectionEventBuffer() != null
-                && mComponentName != null) {
+        if (isContentProtectionEnabled()) {
             mContentProtectionEventProcessor =
                     new ContentProtectionEventProcessor(
                             mManager.getContentProtectionEventBuffer(),
                             mHandler,
                             mSystemServerInterface,
-                            mComponentName.getPackageName());
+                            mComponentName.getPackageName(),
+                            mManager.mOptions.contentProtectionOptions);
         } else {
             mContentProtectionEventProcessor = null;
         }
@@ -956,4 +954,15 @@
     private boolean isContentCaptureReceiverEnabled() {
         return mManager.mOptions.enableReceiver;
     }
+
+    @UiThread
+    private boolean isContentProtectionEnabled() {
+        // Should not be possible for mComponentName to be null here but check anyway
+        // Should not be possible for groups to be empty if receiver is enabled but check anyway
+        return mManager.mOptions.contentProtectionOptions.enableReceiver
+                && mManager.getContentProtectionEventBuffer() != null
+                && mComponentName != null
+                && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty()
+                        || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty());
+    }
 }
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
index b44abf3..aaf90bd 100644
--- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -19,9 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiThread;
+import android.content.ContentCaptureOptions;
 import android.content.pm.ParceledListSlice;
 import android.os.Handler;
-import android.text.InputType;
 import android.util.Log;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.IContentCaptureManager;
@@ -33,10 +33,10 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Stream;
 
 /**
  * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
@@ -47,33 +47,13 @@
 
     private static final String TAG = "ContentProtectionEventProcessor";
 
-    private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
-            Collections.unmodifiableList(
-                    Arrays.asList(
-                            InputType.TYPE_NUMBER_VARIATION_PASSWORD,
-                            InputType.TYPE_TEXT_VARIATION_PASSWORD,
-                            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
-                            InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
-
-    private static final List<String> PASSWORD_TEXTS =
-            Collections.unmodifiableList(
-                    Arrays.asList("password", "pass word", "code", "pin", "credential"));
-
-    private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
-            Collections.unmodifiableList(
-                    Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
-
     private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
 
-    private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
-
     private static final Set<Integer> EVENT_TYPES_TO_STORE =
-            Collections.unmodifiableSet(
-                    new HashSet<>(
-                            Arrays.asList(
-                                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
-                                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
-                                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+            Set.of(
+                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
+                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED);
 
     private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
 
@@ -85,11 +65,7 @@
 
     @NonNull private final String mPackageName;
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public boolean mPasswordFieldDetected = false;
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public boolean mSuspiciousTextDetected = false;
+    @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions;
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @Nullable
@@ -97,15 +73,32 @@
 
     private int mResetLoginRemainingEventsToProcess;
 
+    private boolean mAnyGroupFound = false;
+
+    // Ordered by priority
+    private final List<SearchGroup> mGroupsRequired;
+
+    // Ordered by priority
+    private final List<SearchGroup> mGroupsOptional;
+
+    // Ordered by priority
+    private final List<SearchGroup> mGroupsAll;
+
     public ContentProtectionEventProcessor(
             @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
             @NonNull Handler handler,
             @NonNull IContentCaptureManager contentCaptureManager,
-            @NonNull String packageName) {
+            @NonNull String packageName,
+            @NonNull ContentCaptureOptions.ContentProtectionOptions options) {
         mEventBuffer = eventBuffer;
         mHandler = handler;
         mContentCaptureManager = contentCaptureManager;
         mPackageName = packageName;
+        mOptions = options;
+        mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList();
+        mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList();
+        mGroupsAll =
+                Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList();
     }
 
     /** Main entry point for {@link ContentCaptureEvent} processing. */
@@ -130,9 +123,31 @@
 
     @UiThread
     private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
-        mPasswordFieldDetected |= isPasswordField(event);
-        mSuspiciousTextDetected |= isSuspiciousText(event);
-        if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+        ViewNode viewNode = event.getViewNode();
+        String eventText = ContentProtectionUtils.getEventTextLower(event);
+        String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode);
+        String hintText = ContentProtectionUtils.getHintTextLower(viewNode);
+
+        mGroupsAll.stream()
+                .filter(group -> !group.mFound)
+                .filter(
+                        group ->
+                                group.matches(eventText)
+                                        || group.matches(viewNodeText)
+                                        || group.matches(hintText))
+                .findFirst()
+                .ifPresent(
+                        group -> {
+                            group.mFound = true;
+                            mAnyGroupFound = true;
+                        });
+
+        boolean loginDetected =
+                mGroupsRequired.stream().allMatch(group -> group.mFound)
+                        && mGroupsOptional.stream().filter(group -> group.mFound).count()
+                                >= mOptions.optionalGroupsThreshold;
+
+        if (loginDetected) {
             loginDetected();
         } else {
             maybeResetLoginFlags();
@@ -150,14 +165,13 @@
 
     @UiThread
     private void resetLoginFlags() {
-        mPasswordFieldDetected = false;
-        mSuspiciousTextDetected = false;
-        mResetLoginRemainingEventsToProcess = 0;
+        mGroupsAll.forEach(group -> group.mFound = false);
+        mAnyGroupFound = false;
     }
 
     @UiThread
     private void maybeResetLoginFlags() {
-        if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+        if (mAnyGroupFound) {
             if (mResetLoginRemainingEventsToProcess <= 0) {
                 mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
             } else {
@@ -194,61 +208,21 @@
         }
     }
 
-    private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
-        return isPasswordField(event.getViewNode());
-    }
+    private static final class SearchGroup {
 
-    private boolean isPasswordField(@Nullable ViewNode viewNode) {
-        if (viewNode == null) {
-            return false;
+        @NonNull private final List<String> mSearchStrings;
+
+        public boolean mFound = false;
+
+        SearchGroup(@NonNull List<String> searchStrings) {
+            mSearchStrings = searchStrings;
         }
-        return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
-    }
 
-    private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
-        if (!isAndroidViewNode(viewNode)) {
-            return false;
+        public boolean matches(@Nullable String text) {
+            if (text == null) {
+                return false;
+            }
+            return mSearchStrings.stream().anyMatch(text::contains);
         }
-        int inputType = viewNode.getInputType();
-        return PASSWORD_FIELD_INPUT_TYPES.stream()
-                .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
-    }
-
-    private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
-        if (viewNode.getClassName() != null) {
-            return false;
-        }
-        return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
-    }
-
-    private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
-        String className = viewNode.getClassName();
-        return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
-    }
-
-    private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
-        return isSuspiciousText(ContentProtectionUtils.getEventText(event))
-                || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
-    }
-
-    private boolean isSuspiciousText(@Nullable String text) {
-        if (text == null) {
-            return false;
-        }
-        if (isPasswordText(text)) {
-            return true;
-        }
-        String lowerCaseText = text.toLowerCase();
-        return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
-                .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
-    }
-
-    private boolean isPasswordText(@Nullable String text) {
-        if (text == null) {
-            return false;
-        }
-        String lowerCaseText = text.toLowerCase();
-        return PASSWORD_TEXTS.stream()
-                .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
     }
 }
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
index 9abf6f1..1ecac7f 100644
--- a/core/java/android/view/contentprotection/ContentProtectionUtils.java
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -28,33 +28,39 @@
  */
 public final class ContentProtectionUtils {
 
-    /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+    /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */
     @Nullable
-    public static String getEventText(@NonNull ContentCaptureEvent event) {
+    public static String getEventTextLower(@NonNull ContentCaptureEvent event) {
         CharSequence text = event.getText();
         if (text == null) {
             return null;
         }
-        return text.toString();
+        return text.toString().toLowerCase();
     }
 
-    /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+    /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */
     @Nullable
-    public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
-        ViewNode viewNode = event.getViewNode();
+    public static String getViewNodeTextLower(@Nullable ViewNode viewNode) {
         if (viewNode == null) {
             return null;
         }
-        return getViewNodeText(viewNode);
-    }
-
-    /** Returns the text extracted directly from the {@link ViewNode}, if set. */
-    @Nullable
-    public static String getViewNodeText(@NonNull ViewNode viewNode) {
         CharSequence text = viewNode.getText();
         if (text == null) {
             return null;
         }
-        return text.toString();
+        return text.toString().toLowerCase();
+    }
+
+    /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */
+    @Nullable
+    public static String getHintTextLower(@Nullable ViewNode viewNode) {
+        if (viewNode == null) {
+            return null;
+        }
+        String text = viewNode.getHint();
+        if (text == null) {
+            return null;
+        }
+        return text.toLowerCase();
     }
 }
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 7e06f87..f6ee061 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -6,3 +6,17 @@
     description: "If true, content protection blocklist is mutable and can be updated."
     bug: "301658008"
 }
+
+flag {
+    name: "parse_groups_config_enabled"
+    namespace: "content_protection"
+    description: "If true, content protection groups config will be parsed."
+    bug: "302187922"
+}
+
+flag {
+    name: "setting_ui_enabled"
+    namespace: "content_protection"
+    description: "If true, content protection setting ui is displayed in Settings > Privacy & Security > More security & privacy."
+    bug: "305792348"
+}
diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java
index 6e3d9a8..927874f 100644
--- a/core/java/android/view/displayhash/DisplayHashResultCallback.java
+++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java
@@ -106,7 +106,7 @@
      * {@link android.view.View#generateDisplayHash(String, Rect, Executor,
      * DisplayHashResultCallback)} results in an error and cannot generate a display hash.
      *
-     * @param errorCode One of the values in {@link DisplayHashErrorCode}
+     * @param errorCode the error code
      */
     void onDisplayHashError(@DisplayHashErrorCode int errorCode);
 }
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 56b5fac..2b08eeb 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -22,8 +22,23 @@
 }
 
 flag {
+    name: "expected_presentation_time_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for using expected presentation time of the Choreographer"
+    bug: "278730197"
+    is_fixed_read_only: true
+}
+
+flag {
   name: "set_frame_rate_callback"
   namespace: "core_graphics"
   description: "Enable the `setFrameRate` callback"
   bug: "299946220"
+}
+
+flag {
+    name: "wm_display_refresh_rate_test"
+    namespace: "core_graphics"
+    description: "Adds WindowManager display refresh rate fields to test API"
+    bug: "304475199"
 }
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a92420a..c5114b9 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -47,7 +47,6 @@
 import android.view.MotionEvent.ToolType;
 import android.view.View;
 import android.view.autofill.AutofillId;
-import android.widget.Editor;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -722,9 +721,9 @@
     private boolean mIsStylusHandwritingEnabled;
 
     /**
-     * Set {@code true} if the {@link Editor} has
+     * Set {@code true} if the {@code Editor} has
      * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
-     * {@code false} by default, {@link Editor} must set it {@code true} to indicate that
+     * {@code false} by default, {@code Editor} must set it {@code true} to indicate that
      * it supports stylus handwriting.
      *
      * @param enabled {@code true} if stylus handwriting is enabled.
@@ -736,7 +735,7 @@
     }
 
     /**
-     * Returns {@code true} when an {@link Editor} has stylus handwriting enabled.
+     * Returns {@code true} when an {@code Editor} has stylus handwriting enabled.
      * {@code false} by default.
      * @see #setStylusHandwritingEnabled(boolean)
      * @see InputMethodManager#isStylusHandwritingAvailable()
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f9d8b08..1bc7353 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -172,7 +172,6 @@
             PHASE_CLIENT_HANDLE_HIDE_INSETS,
             PHASE_CLIENT_APPLY_ANIMATION,
             PHASE_CLIENT_CONTROL_ANIMATION,
-            PHASE_CLIENT_DISABLED_USER_ANIMATION,
             PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
             PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
             PHASE_CLIENT_REQUEST_IME_SHOW,
@@ -292,9 +291,6 @@
     /** Started the IME window insets show animation. */
     int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
 
-    /** Checked that the IME is controllable. */
-    int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
-
     /** Collecting insets source controls. */
     int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 70279cc..c83dfe8 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -111,22 +111,6 @@
     private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
 
     /**
-     * Whether the IME supports inline suggestions from the default Autofill service that
-     * provides the input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mServiceSupported;
-
-    /**
-     * Whether the IME supports inline suggestions from the application that provides the
-     * input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mClientSupported;
-
-    /**
      * @hide
      * @see {@link #mHostInputToken}.
      */
@@ -220,14 +204,6 @@
         return Bundle.EMPTY;
     }
 
-    private static boolean defaultServiceSupported() {
-        return true;
-    }
-
-    private static boolean defaultClientSupported() {
-        return true;
-    }
-
     /** @hide */
     abstract static class BaseBuilder {
         abstract Builder setInlinePresentationSpecs(
@@ -240,25 +216,13 @@
         abstract Builder setHostDisplayId(int value);
     }
 
-    /** @hide */
-    public boolean isServiceSupported() {
-        return mServiceSupported;
-    }
-
-    /** @hide */
-    public boolean isClientSupported() {
-        return mClientSupported;
-    }
-
-
-
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -274,9 +238,7 @@
             @NonNull Bundle extras,
             @Nullable IBinder hostInputToken,
             int hostDisplayId,
-            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
-            boolean serviceSupported,
-            boolean clientSupported) {
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mInlinePresentationSpecs = inlinePresentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -293,8 +255,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -378,9 +338,7 @@
     }
 
     /**
-     * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-     *
-     * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
+     * Specifies the UI specification for the inline suggestion tooltip in the response.
      */
     @DataClass.Generated.Member
     public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -401,9 +359,7 @@
                 "extras = " + mExtras + ", " +
                 "hostInputToken = " + mHostInputToken + ", " +
                 "hostDisplayId = " + mHostDisplayId + ", " +
-                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
-                "serviceSupported = " + mServiceSupported + ", " +
-                "clientSupported = " + mClientSupported +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
         " }";
     }
 
@@ -427,9 +383,7 @@
                 && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId
-                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
-                && mServiceSupported == that.mServiceSupported
-                && mClientSupported == that.mClientSupported;
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
     }
 
     @Override
@@ -447,8 +401,6 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         _hash = 31 * _hash + mHostDisplayId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
-        _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
-        _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
         return _hash;
     }
 
@@ -459,8 +411,6 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         int flg = 0;
-        if (mServiceSupported) flg |= 0x100;
-        if (mClientSupported) flg |= 0x200;
         if (mHostInputToken != null) flg |= 0x20;
         if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
         dest.writeInt(flg);
@@ -486,11 +436,9 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         int flg = in.readInt();
-        boolean serviceSupported = (flg & 0x100) != 0;
-        boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
@@ -514,8 +462,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -549,8 +495,6 @@
         private @Nullable IBinder mHostInputToken;
         private int mHostDisplayId;
         private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
-        private boolean mServiceSupported;
-        private boolean mClientSupported;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -683,9 +627,7 @@
         }
 
         /**
-         * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-         *
-         * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
+         * Specifies the UI specification for the inline suggestion tooltip in the response.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -695,38 +637,10 @@
             return this;
         }
 
-        /**
-         * Whether the IME supports inline suggestions from the default Autofill service that
-         * provides the input view.
-         *
-         * Note: The default value is {@code true}.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setServiceSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x100;
-            mServiceSupported = value;
-            return this;
-        }
-
-        /**
-         * Whether the IME supports inline suggestions from the application that provides the
-         * input view.
-         *
-         * Note: The default value is {@code true}.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setClientSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x200;
-            mClientSupported = value;
-            return this;
-        }
-
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -749,12 +663,6 @@
             if ((mBuilderFieldsSet & 0x80) == 0) {
                 mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
             }
-            if ((mBuilderFieldsSet & 0x100) == 0) {
-                mServiceSupported = defaultServiceSupported();
-            }
-            if ((mBuilderFieldsSet & 0x200) == 0) {
-                mClientSupported = defaultClientSupported();
-            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mInlinePresentationSpecs,
@@ -763,14 +671,12 @@
                     mExtras,
                     mHostInputToken,
                     mHostDisplayId,
-                    mInlineTooltipPresentationSpec,
-                    mServiceSupported,
-                    mClientSupported);
+                    mInlineTooltipPresentationSpec);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -778,10 +684,10 @@
     }
 
     @DataClass.Generated(
-            time = 1615798784918L,
-            codegenVersion = "1.0.22",
+            time = 1696889841006L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate  boolean mServiceSupported\nprivate  boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static  boolean defaultServiceSupported()\nprivate static  boolean defaultClientSupported()\npublic  boolean isServiceSupported()\npublic  boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5bb1e93..eeab005 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -100,7 +101,6 @@
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
-import android.widget.Editor;
 import android.window.ImeOnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -1513,6 +1513,7 @@
      * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
      * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
      * called and Stylus touch should continue as normal touch input.
+     *
      * @see #startStylusHandwriting(View)
      */
     public boolean isStylusHandwritingAvailable() {
@@ -1536,6 +1537,7 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
+    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
         final Context fallbackContext = ActivityThread.currentApplication();
@@ -1656,6 +1658,7 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
+    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
         return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
@@ -1691,12 +1694,13 @@
      *               {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
      *               different from the calling process user ID.
      * @return {@link List} of {@link InputMethodSubtype}.
-     * @see #getEnabledInputMethodListAsUser(int)
+     * @see #getEnabledInputMethodListAsUser(UserHandle)
      * @hide
      */
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
+    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
             @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
@@ -2374,16 +2378,16 @@
      * Prepares delegation of starting stylus handwriting session to a different editor in same
      * or different window than the view on which initial handwriting stroke was detected.
      *
-     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
      * its {@link InputConnection} is started. Calling this method starts buffering of stylus
      * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
      * point the handwriting session can be started and the buffered stylus motion events will be
      * delivered to the IME.
      * e.g. Delegation can be used when initial handwriting stroke is
-     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
-     * {@link Editor} is on a different window.
+     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+     * {@code Editor} is on a different window.
      *
-     * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled
+     * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled
      * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
      *
      * @param delegatorView the view that receives initial stylus stroke and delegates it to the
@@ -2402,21 +2406,21 @@
      * different window in a different package than the view on which initial handwriting stroke
      * was detected.
      *
-     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
      * its {@link InputConnection} is started. Calling this method starts buffering of stylus
      * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
      * which point the handwriting session can be started and the buffered stylus motion events will
      * be delivered to the IME.
      * e.g. Delegation can be used when initial handwriting stroke is
-     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
-     * {@link Editor} is on a different window in the given package.
+     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+     * {@code Editor} is on a different window in the given package.
      *
      * <p>Note: If delegator and delegate are in same package use
      * {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
      *
      * @param delegatorView  the view that receives initial stylus stroke and delegates it to the
      * actual editor. Its window must {@link View#hasWindowFocus have focus}.
-     * @param delegatePackageName package name that contains actual {@link Editor} which should
+     * @param delegatePackageName package name that contains actual {@code Editor} which should
      *  start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
      * @see #prepareStylusHandwritingDelegation(View)
      * @see #acceptStylusHandwritingDelegation(View, String)
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 7eee33f..9f0b31b 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -770,7 +770,7 @@
         }
 
         /**
-         * Set the font variation settings. Returns null if no variation is specified.
+         * Set the font variation settings. Set {@code null} if no variation is specified.
          *
          * @see Paint#getFontVariationSettings()
          */
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index c14b510..1e8718c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -14,4 +14,12 @@
     description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled"
     bug: "293898187"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "imm_userhandle_hostsidetests"
+    namespace: "input_method"
+    description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions"
+    bug: "301713309"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index 5be0e3f..78de7e1 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -124,7 +124,7 @@
     void readObject(int id, @Nullable Object value);
 
     /**
-     * Read a color packed into a {@link ColorInt} as a property.
+     * Read a color packed into an int as a property.
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725d..f19a2f9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@
             return false;
         }
 
+        /** See {@link RemoteViews#visitUris(Consumer)}. **/
         public void visitUris(@NonNull Consumer<Uri> visitor) {
-            // Nothing to visit by default
+            // Nothing to visit by default.
         }
     }
 
@@ -761,9 +762,11 @@
     }
 
     /**
-     * Note all {@link Uri} that are referenced internally, with the expectation
-     * that Uri permission grants will need to be issued to ensure the recipient
-     * of this object is able to render its contents.
+     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+     * grants will need to be issued to ensure the recipient of this object is able to render its
+     * contents.
+     * See b/281044385 for more context and examples about what happens when this isn't done
+     * correctly.
      *
      * @hide
      */
@@ -1088,6 +1091,13 @@
         public String getUniqueKey() {
             return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (RemoteViews remoteViews : mList) {
+                remoteViews.visitUris(visitor);
+            }
+        }
     }
 
     /**
@@ -1289,6 +1299,12 @@
         public String getUniqueKey() {
             return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+            items.visitUris(visitor);
+        }
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
@@ -1359,6 +1375,13 @@
         public int getActionTag() {
             return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated
+            //  visitUris method in the Intent class, since it can contain other intents. Otherwise,
+            //  the basic thing to do here would be just visitor.accept(intent.getData()).
+        }
     }
 
     /**
@@ -1434,6 +1457,11 @@
         public int getActionTag() {
             return SET_ON_CLICK_RESPONSE_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+        }
     }
 
     /**
@@ -1504,6 +1532,11 @@
         public int getActionTag() {
             return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+        }
     }
 
     /** @hide **/
@@ -2063,6 +2096,7 @@
                     final Icon icon = (Icon) getParameterValue(null);
                     if (icon != null) visitIconUri(icon, visitor);
                     break;
+                // TODO(b/281044385): Should we do anything about type BUNDLE?
             }
         }
     }
@@ -2812,7 +2846,7 @@
         }
 
         @Override
-        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
             mNestedViews.visitUris(visitor);
         }
     }
@@ -7262,6 +7296,15 @@
                         Math.max(mViewTypeCount, 1));
             }
         }
+
+        /**
+         * See {@link RemoteViews#visitUris(Consumer)}.
+         */
+        private void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (RemoteViews view : mViews) {
+                view.visitUris(visitor);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e8281ea..6da6a64 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,6 +28,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
 
+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 android.R;
@@ -865,6 +866,7 @@
     private final boolean mUseTextPaddingForUiTranslation;
 
     private boolean mUseBoundsForWidth;
+    @Nullable private Paint.FontMetrics mMinimumFontMetrics;
 
     @ViewDebug.ExportedProperty(category = "text")
     @UnsupportedAppUsage
@@ -4901,6 +4903,58 @@
     }
 
     /**
+     * Set the minimum font metrics used for line spacing.
+     *
+     * <p>
+     * {@code null} is the default value. If {@code null} is set or left as default, the font
+     * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+     *
+     * <p>
+     * The minimum meaning here is the minimum value of line spacing: maximum value of
+     * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+     *
+     * <p>
+     * By setting this value, each line will have minimum line spacing regardless of the text
+     * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
+     * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+     * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
+     * the line spacing for Japanese is reserved if the TextView contains English text. If the
+     * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
+     * metrics is used.
+     *
+     * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
+     *                           obtained by
+     *                           {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+     * @see #getMinimumFontMetrics()
+     * @see Layout#getMinimumFontMetrics()
+     * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     */
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+        mMinimumFontMetrics = minimumFontMetrics;
+    }
+
+    /**
+     * Get the minimum font metrics used for line spacing.
+     *
+     * @see #setMinimumFontMetrics(Paint.FontMetrics)
+     * @see Layout#getMinimumFontMetrics()
+     * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+     *
+     * @return a minimum font metrics. {@code null} for using the value obtained by
+     *         {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+     */
+    @Nullable
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public Paint.FontMetrics getMinimumFontMetrics() {
+        return mMinimumFontMetrics;
+    }
+
+    /**
      * @return whether fallback line spacing is enabled, {@code true} by default
      *
      * @see #setFallbackLineSpacing(boolean)
@@ -9854,7 +9908,10 @@
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
                 outAttrs.setInitialSurroundingText(mText);
                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
-
+                if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
+                        && isAutoHandwritingEnabled()) {
+                    outAttrs.setStylusHandwritingEnabled(true);
+                }
                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
                 gestures.add(SelectGesture.class);
                 gestures.add(SelectRangeGesture.class);
@@ -10680,7 +10737,8 @@
 
             if (hintBoring == UNKNOWN_BORING) {
                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
-                        isFallbackLineSpacingForBoringLayout(), mHintBoring);
+                        isFallbackLineSpacingForBoringLayout(),
+                        mMinimumFontMetrics, mHintBoring);
                 if (hintBoring != null) {
                     mHintBoring = hintBoring;
                 }
@@ -10729,7 +10787,8 @@
                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
                                 mLineBreakStyle, mLineBreakWordStyle))
-                        .setUseBoundsForWidth(mUseBoundsForWidth);
+                        .setUseBoundsForWidth(mUseBoundsForWidth)
+                        .setMinimumFontMetrics(mMinimumFontMetrics);
                 if (shouldEllipsize) {
                     builder.setEllipsize(mEllipsize)
                             .setEllipsizedWidth(ellipsisWidth);
@@ -10793,12 +10852,13 @@
                             mLineBreakStyle, mLineBreakWordStyle))
                     .setUseBoundsForWidth(mUseBoundsForWidth)
                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
-                    .setEllipsizedWidth(ellipsisWidth);
+                    .setEllipsizedWidth(ellipsisWidth)
+                    .setMinimumFontMetrics(mMinimumFontMetrics);
             result = builder.build();
         } else {
             if (boring == UNKNOWN_BORING) {
                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
-                        isFallbackLineSpacingForBoringLayout(), mBoring);
+                        isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
                 if (boring != null) {
                     mBoring = boring;
                 }
@@ -10812,7 +10872,7 @@
                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                 boring, mIncludePad, null, wantWidth,
                                 isFallbackLineSpacingForBoringLayout(),
-                                mUseBoundsForWidth);
+                                mUseBoundsForWidth, mMinimumFontMetrics);
                     } else {
                         result = new BoringLayout(
                                 mTransformed,
@@ -10826,7 +10886,8 @@
                                 wantWidth,
                                 null,
                                 boring,
-                                mUseBoundsForWidth);
+                                mUseBoundsForWidth,
+                                mMinimumFontMetrics);
                     }
 
                     if (useSaved) {
@@ -10838,7 +10899,7 @@
                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                 boring, mIncludePad, effectiveEllipsize,
                                 ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
-                                mUseBoundsForWidth);
+                                mUseBoundsForWidth, mMinimumFontMetrics);
                     } else {
                         result = new BoringLayout(
                                 mTransformed,
@@ -10852,7 +10913,8 @@
                                 ellipsisWidth,
                                 effectiveEllipsize,
                                 boring,
-                                mUseBoundsForWidth);
+                                mUseBoundsForWidth,
+                                mMinimumFontMetrics);
                     }
                 }
             }
@@ -10871,7 +10933,8 @@
                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
                             mLineBreakStyle, mLineBreakWordStyle))
-                    .setUseBoundsForWidth(mUseBoundsForWidth);
+                    .setUseBoundsForWidth(mUseBoundsForWidth)
+                    .setMinimumFontMetrics(mMinimumFontMetrics);
             if (shouldEllipsize) {
                 builder.setEllipsize(effectiveEllipsize)
                         .setEllipsizedWidth(ellipsisWidth);
@@ -10999,7 +11062,7 @@
 
             if (des < 0) {
                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
-                        isFallbackLineSpacingForBoringLayout(), mBoring);
+                        isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
                 if (boring != null) {
                     mBoring = boring;
                 }
@@ -11039,7 +11102,8 @@
 
                 if (hintDes < 0) {
                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
-                            isFallbackLineSpacingForBoringLayout(), mHintBoring);
+                            isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics,
+                            mHintBoring);
                     if (hintBoring != null) {
                         mHintBoring = hintBoring;
                     }
@@ -11251,7 +11315,8 @@
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
                         mLineBreakStyle, mLineBreakWordStyle))
-                .setUseBoundsForWidth(mUseBoundsForWidth);
+                .setUseBoundsForWidth(mUseBoundsForWidth)
+                .setMinimumFontMetrics(mMinimumFontMetrics);
 
         final StaticLayout layout = layoutBuilder.build();
 
@@ -15555,6 +15620,9 @@
     private void ensureIterableTextForAccessibilitySelectable() {
         if (!(mText instanceof Spannable)) {
             setText(mText, BufferType.SPANNABLE);
+            if (getLayout() == null) {
+                assumeLayout();
+            }
         }
     }
 
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index f53737a..5562360 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -49,7 +49,7 @@
     private final int mSwipeEdge;
 
     /**
-     * Creates a new {@link BackMotionEvent} instance.
+     * Creates a new {@link BackEvent} instance.
      *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl
index 2efb68a..ec8b66d 100644
--- a/core/java/android/window/IRemoteTransition.aidl
+++ b/core/java/android/window/IRemoteTransition.aidl
@@ -59,4 +59,12 @@
     void mergeAnimation(in IBinder transition, in TransitionInfo info,
             in SurfaceControl.Transaction t, in IBinder mergeTarget,
             in IRemoteTransitionFinishedCallback finishCallback);
+
+    /**
+     * Called when a different handler has consumed the transition
+     *
+     * @param transition An identifier for the transition that was consumed.
+     * @param aborted Whether the transition is aborted or not.
+     */
+    void onTransitionConsumed(in IBinder transition, in boolean aborted);
 }
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index e1a2b94..2736b68 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -211,7 +211,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(
+                    displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
                     + session.reason, session.traceCookie);
@@ -251,16 +255,21 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_SELF);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
+                    /* smoothSwitchOnly= */ false);
             transactionChanged = true;
-            Trace.endAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie);
+            Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
+                    session.traceCookie);
         }
 
         // Global flags
         if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
             mTransaction.setEarlyWakeupEnd();
             transactionChanged = true;
-            Trace.endAsyncSection("PerfHint-early_wakeup" + session.reason, session.traceCookie);
+            Trace.endAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie);
         }
         if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
             mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 43fa0be..4e0f9a5 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -88,6 +88,26 @@
      */
     public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
 
+    /**
+     * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the
+     * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments.
+     *
+     * This is only allowed for system organizers. See
+     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+     * ITaskFragmentOrganizer, boolean)}
+     */
+    public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12;
+
+    /**
+     * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the
+     * TaskFragment to the top of the Task above all the other Activities and TaskFragments.
+     *
+     * This is only allowed for system organizers. See
+     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+     * ITaskFragmentOrganizer, boolean)}
+     */
+    public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -101,7 +121,9 @@
             OP_TYPE_SET_ANIMATION_PARAMS,
             OP_TYPE_SET_RELATIVE_BOUNDS,
             OP_TYPE_REORDER_TO_FRONT,
-            OP_TYPE_SET_ISOLATED_NAVIGATION
+            OP_TYPE_SET_ISOLATED_NAVIGATION,
+            OP_TYPE_REORDER_TO_BOTTOM_OF_TASK,
+            OP_TYPE_REORDER_TO_TOP_OF_TASK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index 841354a..e6eeca4 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -35,17 +35,21 @@
 
     private final boolean mVisible;
 
+    private final boolean mHasDirectActivity;
+
     public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
-            boolean visible) {
+            boolean visible, boolean hasDirectActivity) {
         mConfiguration.setTo(configuration);
         mDisplayId = displayId;
         mVisible = visible;
+        mHasDirectActivity = hasDirectActivity;
     }
 
     public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
         mConfiguration.setTo(info.getConfiguration());
         mDisplayId = info.mDisplayId;
         mVisible = info.mVisible;
+        mHasDirectActivity = info.mHasDirectActivity;
     }
 
     /** The {@link Configuration} of the parent Task */
@@ -68,6 +72,14 @@
     }
 
     /**
+     * Whether the parent Task has any direct child activity, which is not embedded in any
+     * TaskFragment, or not
+     */
+    public boolean hasDirectActivity() {
+        return mHasDirectActivity;
+    }
+
+    /**
      * Returns {@code true} if the parameters which are important for task fragment
      * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}.
      * Note that this method is usually called with
@@ -80,7 +92,7 @@
             return false;
         }
         return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
-                && mVisible == that.mVisible;
+                && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity;
     }
 
     @WindowConfiguration.WindowingMode
@@ -94,6 +106,7 @@
                 + "config=" + mConfiguration
                 + ", displayId=" + mDisplayId
                 + ", visible=" + mVisible
+                + ", hasDirectActivity=" + mHasDirectActivity
                 + "}";
     }
 
@@ -114,7 +127,8 @@
         final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
         return mConfiguration.equals(that.mConfiguration)
                 && mDisplayId == that.mDisplayId
-                && mVisible == that.mVisible;
+                && mVisible == that.mVisible
+                && mHasDirectActivity == that.mHasDirectActivity;
     }
 
     @Override
@@ -122,6 +136,7 @@
         int result = mConfiguration.hashCode();
         result = 31 * result + mDisplayId;
         result = 31 * result + (mVisible ? 1 : 0);
+        result = 31 * result + (mHasDirectActivity ? 1 : 0);
         return result;
     }
 
@@ -130,12 +145,14 @@
         mConfiguration.writeToParcel(dest, flags);
         dest.writeInt(mDisplayId);
         dest.writeBoolean(mVisible);
+        dest.writeBoolean(mHasDirectActivity);
     }
 
     private TaskFragmentParentInfo(Parcel in) {
         mConfiguration.readFromParcel(in);
         mDisplayId = in.readInt();
         mVisible = in.readBoolean();
+        mHasDirectActivity = in.readBoolean();
     }
 
     public static final Creator<TaskFragmentParentInfo> CREATOR =
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index d2a16a3..1a2d202 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -361,6 +362,15 @@
     }
 
     /**
+     * Whether this transition contains any changes to the window hierarchy,
+     * including keyguard visibility.
+     */
+    public boolean hasChangesOrSideEffects() {
+        return !mChanges.isEmpty() || isKeyguardGoingAway()
+                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+    }
+
+    /**
      * Whether this transition includes keyguard going away.
      */
     public boolean isKeyguardGoingAway() {
@@ -392,6 +402,18 @@
 
     @Override
     public String toString() {
+        return toString("");
+    }
+
+    /**
+     * Returns a string representation of this transition info.
+     * @hide
+     */
+    public String toString(@NonNull String prefix) {
+        final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty();
+        final String innerPrefix = shouldPrettyPrint ? prefix + "    " : "";
+        final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : "";
+        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)
@@ -403,12 +425,15 @@
             sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
         }
         sb.append("] c=[");
+        sb.append(perChangeLineStart);
         for (int i = 0; i < mChanges.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
+                sb.append(perChangeLineStart);
             }
             sb.append(mChanges.get(i));
         }
+        sb.append(changesLineStart);
         sb.append("]}");
         return sb.toString();
     }
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 932608a3..bd54e14 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -62,13 +62,16 @@
     /** The transition flags known at the time of the request. These may not be complete. */
     private final int mFlags;
 
+    /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
+    private final int mDebugId;
+
     /** constructor override */
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition) {
         this(type, triggerTask, null /* pipTask */,
-                remoteTransition, null /* displayChange */, 0 /* flags */);
+                remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */);
     }
 
     /** constructor override */
@@ -78,16 +81,29 @@
             @Nullable RemoteTransition remoteTransition,
             int flags) {
         this(type, triggerTask, null /* pipTask */,
-                remoteTransition, null /* displayChange */, flags);
+                remoteTransition, null /* displayChange */, flags, -1 /* debugId */);
     }
 
+        /** constructor override */
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
             int flags) {
-        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags);
+        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags,
+                -1 /* debugId */);
+    }
+
+    /** constructor override */
+    public TransitionRequestInfo(
+            @WindowManager.TransitionType int type,
+            @Nullable ActivityManager.RunningTaskInfo triggerTask,
+            @Nullable ActivityManager.RunningTaskInfo pipTask,
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange,
+            int flags) {
+        this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */);
     }
 
     /** @hide */
@@ -270,7 +286,7 @@
         };
 
         @DataClass.Generated(
-                time = 1695667226050L,
+                time = 1697564781403L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
                 inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nprivate  boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -318,6 +334,8 @@
      *   (if size is changing).
      * @param flags
      *   The transition flags known at the time of the request. These may not be complete.
+     * @param debugId
+     *   This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!
      */
     @DataClass.Generated.Member
     public TransitionRequestInfo(
@@ -326,7 +344,8 @@
             @Nullable ActivityManager.RunningTaskInfo pipTask,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
-            int flags) {
+            int flags,
+            int debugId) {
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
@@ -335,6 +354,7 @@
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
+        this.mDebugId = debugId;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -392,6 +412,14 @@
     }
 
     /**
+     * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!
+     */
+    @DataClass.Generated.Member
+    public int getDebugId() {
+        return mDebugId;
+    }
+
+    /**
      * If non-null, the task containing the activity whose lifecycle change (start or
      * finish) has caused this transition to occur.
      */
@@ -443,7 +471,8 @@
                 "pipTask = " + mPipTask + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
                 "displayChange = " + mDisplayChange + ", " +
-                "flags = " + mFlags +
+                "flags = " + mFlags + ", " +
+                "debugId = " + mDebugId +
         " }";
     }
 
@@ -465,6 +494,7 @@
         if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
         if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
         dest.writeInt(mFlags);
+        dest.writeInt(mDebugId);
     }
 
     @Override
@@ -485,6 +515,7 @@
         RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
         TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
         int flags = in.readInt();
+        int debugId = in.readInt();
 
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
@@ -494,6 +525,7 @@
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
+        this.mDebugId = debugId;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -513,10 +545,10 @@
     };
 
     @DataClass.Generated(
-            time = 1695667226088L,
+            time = 1697564781438L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
-            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nprivate final  int mDebugId\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index c2b5196..d9b5b2d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -804,7 +804,7 @@
      * Sets/removes the always on top flag for this {@code windowContainer}. See
      * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
      * Please note that this method is only intended to be used for a
-     * {@link com.android.server.wm.DisplayArea}.
+     * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}.
      *
      * <p>
      *     Setting always on top to {@code True} will also make the {@code windowContainer} to move
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 0ee07bb..3d4bc2f 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,9 +16,6 @@
 
 package android.window;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -37,8 +34,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -60,18 +57,6 @@
  * @hide
  */
 public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
-    @Retention(SOURCE)
-    @IntDef({
-        BACK_CALLBACK_ENABLED,
-        BACK_CALLBACK_DISABLED,
-        BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS
-    })
-    public @interface OnBackInvokedCallbackType {}
-
-    public static final int BACK_CALLBACK_ENABLED = 0;
-    public static final int BACK_CALLBACK_DISABLED = 1;
-    public static final int BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS = 2;
-
     private IWindowSession mWindowSession;
     private IWindow mWindow;
     private static final String TAG = "WindowOnBackDispatcher";
@@ -283,6 +268,13 @@
     }
 
     /**
+     * Returns false if the legacy back behavior should be used.
+     */
+    public boolean isOnBackInvokedCallbackEnabled() {
+        return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext());
+    }
+
+    /**
      * Dump information about this WindowOnBackInvokedDispatcher
      * @param prefix the prefix that will be prepended to each line of the produced output
      * @param writer the writer that will receive the resulting text
@@ -395,20 +387,6 @@
         }
     }
 
-    /** Returns false if the legacy back behavior should be used. */
-    public boolean isOnBackInvokedCallbackEnabled() {
-        return isOnBackInvokedCallbackEnabled(mChecker.getContext());
-    }
-
-    /**
-     * Returns true if system gesture exclusion is needed for global gesture compatibility with
-     * windowSwipeToDismiss styleable.
-     */
-    public boolean isSystemGestureExclusionNeeded() {
-        return Checker.getBackCallbackType(mChecker.getContext())
-                == BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS;
-    }
-
     /**
      * Returns false if the legacy back behavior should be used.
      * <p>
@@ -416,7 +394,7 @@
      * {@link OnBackInvokedCallback}.
      */
     public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) {
-        return Checker.getBackCallbackType(context) == BACK_CALLBACK_ENABLED;
+        return Checker.isOnBackInvokedCallbackEnabled(context);
     }
 
     @Override
@@ -468,29 +446,28 @@
             return mContext.get();
         }
 
-        @OnBackInvokedCallbackType
-        private static int getBackCallbackType(@Nullable Context context) {
+        private static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) {
             // new back is enabled if the feature flag is enabled AND the app does not explicitly
             // request legacy back.
             boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK;
             if (!featureFlagEnabled) {
-                return BACK_CALLBACK_DISABLED;
+                return false;
             }
 
             if (ALWAYS_ENFORCE_PREDICTIVE_BACK) {
-                Log.i(TAG, "getBackCallbackType: always enable");
-                return BACK_CALLBACK_ENABLED;
+                return true;
             }
 
             // If the context is null, return false to use legacy back.
             if (context == null) {
                 Log.w(TAG, "OnBackInvokedCallback is not enabled because context is null.");
-                return BACK_CALLBACK_DISABLED;
+                return false;
             }
 
             boolean requestsPredictiveBack = false;
 
             // Check if the context is from an activity.
+            Context originalContext = context;
             while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
                 context = ((ContextWrapper) context).getBaseContext();
             }
@@ -539,8 +516,10 @@
                     // 3. windowSwipeToDismiss=false should be respected for apps not opted in,
                     //    which disables PB & onBackPressed caused by BackAnimController's
                     //    setTrigger(true)
+                    // Use the original context to resolve the styled attribute so that they stay
+                    // true to the window.
                     TypedArray windowAttr =
-                            context.obtainStyledAttributes(
+                            originalContext.obtainStyledAttributes(
                                     new int[] {android.R.attr.windowSwipeToDismiss});
                     boolean windowSwipeToDismiss = true;
                     if (windowAttr.getIndexCount() > 0) {
@@ -552,15 +531,11 @@
                         Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss);
                     }
 
-                    if (!windowSwipeToDismiss) {
-                        return BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS;
-                    } else {
-                        return BACK_CALLBACK_ENABLED;
-                    }
+                    requestsPredictiveBack = windowSwipeToDismiss;
                 }
             }
 
-            return requestsPredictiveBack ? BACK_CALLBACK_ENABLED : BACK_CALLBACK_DISABLED;
+            return requestsPredictiveBack;
         }
     }
 }
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 611da3c..c81c9ec 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -129,7 +129,6 @@
     @SuppressLint({"OnNameExpected", "ExecutorRegistration"})
     // Suppress lint because this is a legacy named function and doesn't have an optional param
     // for executor.
-    // TODO(b/259347943): Update documentation for U.
     /**
      * Here we override to prevent WindowProviderService from invoking
      * {@link Application.registerComponentCallback}, which will result in callback registered
@@ -155,7 +154,7 @@
     }
 
     /**
-     * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+     * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for
      * low memory and trim memory events.
      */
     @Override
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 1b98806..ccbf4a9 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -9,3 +9,11 @@
     is_fixed_read_only: true
     bug: "292032926"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "explicit_refresh_rate_hints"
+    description: "Performance related hints during transitions"
+    is_fixed_read_only: true
+    bug: "300019131"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7c931cd..4705dc5 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -8,8 +8,40 @@
 }
 
 flag {
+    name: "defer_display_updates"
+    namespace: "window_manager"
+    description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
+    bug: "259220649"
+    is_fixed_read_only: true
+}
+
+flag {
   name: "close_to_square_config_includes_status_bar"
   namespace: "windowing_frontend"
   description: "On close to square display, when necessary, configuration includes status bar"
   bug: "291870756"
 }
+
+flag {
+  name: "introduce_smoother_dimmer"
+  namespace: "windowing_frontend"
+  description: "Refactor dim to fix flickers"
+  bug: "295291019"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "transit_ready_tracking"
+  namespace: "windowing_frontend"
+  description: "Enable accurate transition readiness tracking"
+  bug: "294925498"
+}
+
+
+flag {
+    name: "wallpaper_offset_async"
+    namespace: "windowing_frontend"
+    description: "Do not synchronise the wallpaper offset"
+    bug: "293248754"
+    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 ec5d4ff..24dc6db 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -22,3 +22,10 @@
     description: "Whether the TaskFragment system organizer feature is enabled"
     bug: "284050041"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "window_state_resize_item_flag"
+    description: "Whether to dispatch window resize through ClientTransaction is enabled"
+    bug: "301870955"
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 5dd558a..987c14c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -28,15 +28,19 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.view.View;
 import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.Flags;
 import android.widget.AdapterView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -207,6 +211,11 @@
                 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
     }
 
+    @VisibleForTesting
+    public AlertDialog getMenuDialog() {
+        return mMenuDialog;
+    }
+
     private AlertDialog createMenuDialog() {
         final String dialogTitle =
                 getString(R.string.accessibility_select_shortcut_menu_title);
@@ -216,12 +225,25 @@
                 .setAdapter(mTargetAdapter, /* listener= */ null)
                 .setOnDismissListener(dialog -> finish());
 
-        if (isUserSetupCompleted(this)) {
+        boolean allowEditing = isUserSetupCompleted(this);
+        boolean showWhenLocked = false;
+        if (Flags.allowShortcutChooserOnLockscreen()) {
+            final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+            if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+                allowEditing = false;
+                showWhenLocked = true;
+            }
+        }
+        if (allowEditing) {
             final String positiveButtonText =
                     getString(R.string.edit_accessibility_shortcut_menu_button);
             builder.setPositiveButton(positiveButtonText, /* listener= */ null);
         }
 
-        return builder.create();
+        final AlertDialog dialog = builder.create();
+        if (showWhenLocked) {
+            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        }
+        return dialog;
     }
 }
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ba87caa..a65877c 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -20,6 +20,7 @@
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.HotwordTrainingData;
 import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 import com.android.internal.infra.AndroidFuture;
@@ -59,6 +60,12 @@
     void onRejected(in HotwordRejectedResult result);
 
     /**
+     * Called by {@link HotwordDetectionService} to egress training data to the
+     * {@link HotwordDetector}.
+     */
+    void onTrainingData(in HotwordTrainingData data);
+
+    /**
      * Called when the detection fails due to an error occurs in the
      * {@link HotwordDetectionService}.
      *
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 314ed69..68e2b48 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -359,6 +359,12 @@
             in IHotwordRecognitionStatusCallback callback);
 
     /**
+     * Test API to reset training data egress count for test.
+     */
+    @EnforcePermission("RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT")
+    void resetHotwordTrainingDataEgressCountForTest();
+
+    /**
      * Starts to listen the status of visible activity.
      */
     void startListeningVisibleActivityChanged(in IBinder token);
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe5..71bbccb 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@
             }
             return true;
         }
-        return false;
+        return super.onKeyDown(keyCode,event);
     }
 
     @Override
@@ -268,7 +268,7 @@
             stopWarp();
             return true;
         }
-        return false;
+        return super.onKeyUp(keyCode,event);
     }
 
     private void startWarp() {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 2909b6a..e494346 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -414,11 +414,6 @@
             "dark_launch_remote_prediction_service_enabled";
 
     /**
-     * (boolean) Whether to enable pinch resizing for PIP.
-     */
-    public static final String PIP_PINCH_RESIZE = "pip_pinch_resize";
-
-    /**
      * (boolean) Whether to enable stashing for PIP.
      */
     public static final String PIP_STASHING = "pip_stashing";
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index cb2d934..77e1502 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,10 +81,27 @@
         public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag(
                 "persist.sysui.notification.propagate_channel_updates_to_conversations");
 
-        // TODO: b/291907312 - remove feature flags
-        /** Gating the NMS->NotificationAttentionHelper buzzBeepBlink refactor */
-        public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag(
-                "persist.debug.sysui.notification.enable_attention_helper_refactor");
+        // TODO b/291899544: for released flags, use resource config values
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_t1", 5000);
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_t2", 3000);
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_VOLUME1 = devFlag(
+                "persist.debug.sysui.notification.notif_volume1", 30);
+        public static final Flag NOTIF_VOLUME2 = devFlag(
+                "persist.debug.sysui.notification.notif_volume2", 0);
+        /** Value used by polite notif. feature. -1 to ignore the counter */
+        public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
+        /**
+         * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1,
+         * rule2
+         */
+        public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
 
         /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */
         public static final Flag VISIT_RISKY_URIS = devFlag(
@@ -97,6 +114,10 @@
     public interface FlagResolver {
         /** Is the flag enabled? */
         boolean isEnabled(Flag flag);
+        /** Get the flag value (integer) */
+        int getIntValue(Flag flag);
+        /** Get the flag value (string) */
+        String getStringValue(Flag flag);
     }
 
     /** The primary, immutable resolver returned by getResolver() */
@@ -134,6 +155,22 @@
     }
 
     /**
+     * Creates a flag that with a default integer value in debuggable builds.
+     */
+    @VisibleForTesting
+    public static Flag devFlag(String name, int defaultValue) {
+        return new Flag(name, defaultValue, null);
+    }
+
+    /**
+     * Creates a flag that with a default string value in debuggable builds.
+     */
+    @VisibleForTesting
+    public static Flag devFlag(String name, String defaultValue) {
+        return new Flag(name, defaultValue, null);
+    }
+
+    /**
      * Creates a flag that is disabled by default in debuggable builds.
      * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
      * If this flag's SystemProperty is not set, the flag can be enabled by setting the
@@ -161,6 +198,8 @@
     public static final class Flag {
         public final String mSysPropKey;
         public final boolean mDefaultValue;
+        public final int mDefaultIntValue;
+        public final String mDefaultStringValue;
         @Nullable
         public final Flag mDebugDefault;
 
@@ -170,6 +209,24 @@
             mSysPropKey = sysPropKey;
             mDefaultValue = defaultValue;
             mDebugDefault = debugDefault;
+            mDefaultIntValue = 0;
+            mDefaultStringValue = null;
+        }
+
+        public Flag(@NonNull String sysPropKey, int defaultValue, @Nullable Flag debugDefault) {
+            mSysPropKey = sysPropKey;
+            mDefaultIntValue = defaultValue;
+            mDebugDefault = debugDefault;
+            mDefaultValue = false;
+            mDefaultStringValue = null;
+        }
+
+        public Flag(@NonNull String sysPropKey, String defaultValue, @Nullable Flag debugDefault) {
+            mSysPropKey = sysPropKey;
+            mDefaultStringValue = defaultValue;
+            mDebugDefault = debugDefault;
+            mDefaultValue = false;
+            mDefaultIntValue = 0;
         }
     }
 
@@ -181,6 +238,16 @@
         public boolean isEnabled(Flag flag) {
             return flag.mDefaultValue;
         }
+
+        @Override
+        public int getIntValue(Flag flag) {
+            return flag.mDefaultIntValue;
+        }
+
+        @Override
+        public String getStringValue(Flag flag) {
+            return flag.mDefaultStringValue;
+        }
     }
 
     /** Implementation of the interface used in debuggable builds. */
@@ -199,5 +266,23 @@
         public boolean getBoolean(String key, boolean defaultValue) {
             return SystemProperties.getBoolean(key, defaultValue);
         }
+
+        /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+        @VisibleForTesting
+        public int getIntValue(Flag flag) {
+            if (flag.mDebugDefault == null) {
+                return SystemProperties.getInt(flag.mSysPropKey, flag.mDefaultIntValue);
+            }
+            return SystemProperties.getInt(flag.mSysPropKey, getIntValue(flag.mDebugDefault));
+        }
+
+        /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+        @VisibleForTesting
+        public String getStringValue(Flag flag) {
+            if (flag.mDebugDefault == null) {
+                return SystemProperties.get(flag.mSysPropKey, flag.mDefaultStringValue);
+            }
+            return SystemProperties.get(flag.mSysPropKey, getStringValue(flag.mDebugDefault));
+        }
     }
 }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 58376a7..0801dd8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,16 +62,14 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Deque;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
 
 /**
  * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -89,6 +87,8 @@
             DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
             DocumentsContract.QUERY_ARG_MIME_TYPES);
 
+    private static final int MAX_RESULTS_NUMBER = 23;
+
     private static String joinNewline(String... args) {
         return TextUtils.join("\n", args);
     }
@@ -375,62 +375,53 @@
     }
 
     /**
-     * This method is similar to
-     * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
-     * all children documents including hidden directories/files.
-     *
-     * <p>
-     * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
-     * reasons. This method may show privacy sensitive data, so its usage should only be in
-     * restricted modes.
-     *
-     * @param parentDocumentId the directory to return children for.
-     * @param projection list of {@link Document} columns to put into the
-     *            cursor. If {@code null} all supported columns should be
-     *            included.
-     * @param sortOrder how to order the rows, formatted as an SQL
-     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
-     *            Passing {@code null} will use the default sort order, which
-     *            may be unordered. This ordering is a hint that can be used to
-     *            prioritize how data is fetched from the network, but UI may
-     *            always enforce a specific ordering
-     * @throws FileNotFoundException when parent document doesn't exist or query fails
+     * WARNING: this method should really be {@code final}, but for the backward compatibility it's
+     * not; new classes that extend {@link FileSystemProvider} should override
+     * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.
      */
-    protected Cursor queryChildDocumentsShowAll(
-            String parentDocumentId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
-    }
-
     @Override
-    public Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder)
+    public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
-        // Access to some directories is hidden for privacy reasons.
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);
     }
 
-    private Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder,
-            @NonNull Predicate<File> filter) throws FileNotFoundException {
-        final File parent = getFileForDocId(parentDocumentId);
-        final MatrixCursor result = new DirectoryCursor(
-                resolveProjection(projection), parentDocumentId, parent);
+    /**
+     * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it
+     * could return <b>all</b> content of the directory, <b>including restricted (hidden)
+     * directories and files</b>.
+     * <p>
+     * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and
+     * {@code Android/obb/} on the external storage) are hidden for privacy reasons.
+     * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care.
+     */
+    @Override
+    public final Cursor queryChildDocumentsForManage(String documentId, String[] projection,
+            String sortOrder) throws FileNotFoundException {
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);
+    }
 
-        if (!filter.test(parent)) {
-            Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId);
+    protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder,
+            boolean includeHidden) throws FileNotFoundException {
+        final File parent = getFileForDocId(documentId);
+        final MatrixCursor result = new DirectoryCursor(
+                resolveProjection(projection), documentId, parent);
+
+        if (!parent.isDirectory()) {
+            Log.w(TAG, '"' + documentId + "\" is not a directory");
             return result;
         }
 
-        if (parent.isDirectory()) {
-            for (File file : FileUtils.listFilesOrEmpty(parent)) {
-                if (filter.test(file)) {
-                    includeFile(result, null, file);
-                }
-            }
-        } else {
-            Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
+        if (!includeHidden && shouldHideDocument(documentId)) {
+            Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");
+            return result;
         }
+
+        for (File file : FileUtils.listFilesOrEmpty(parent)) {
+            if (!includeHidden && shouldHideDocument(file)) continue;
+
+            includeFile(result, null, file);
+        }
+
         return result;
     }
 
@@ -452,23 +443,29 @@
      *
      * @see ContentResolver#EXTRA_HONORED_ARGS
      */
-    protected final Cursor querySearchDocuments(
-            File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
-            throws FileNotFoundException {
+    protected final Cursor querySearchDocuments(File folder, String[] projection,
+            Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
-        final List<File> pending = new ArrayList<>();
-        pending.add(folder);
-        while (!pending.isEmpty() && result.getCount() < 24) {
-            final File file = pending.remove(0);
-            if (shouldHide(file)) continue;
+
+        // We'll be a running a BFS here.
+        final Queue<File> pending = new ArrayDeque<>();
+        pending.offer(folder);
+
+        while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) {
+            final File file = pending.poll();
+
+            // Skip hidden documents (both files and directories)
+            if (shouldHideDocument(file)) continue;
 
             if (file.isDirectory()) {
                 for (File child : FileUtils.listFilesOrEmpty(file)) {
-                    pending.add(child);
+                    pending.offer(child);
                 }
             }
-            if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
-                    queryArgs)) {
+
+            if (exclusion.contains(file.getAbsolutePath())) continue;
+
+            if (matchSearchQueryArguments(file, queryArgs)) {
                 includeFile(result, null, file);
             }
         }
@@ -612,26 +609,23 @@
 
         final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
         if (flagIndex != -1) {
+            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
             int flags = 0;
             if (file.canWrite()) {
-                if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+                flags |= Document.FLAG_SUPPORTS_DELETE;
+                flags |= Document.FLAG_SUPPORTS_RENAME;
+                flags |= Document.FLAG_SUPPORTS_MOVE;
+                if (isDir) {
                     flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
-
-                    if (shouldBlockFromTree(docId)) {
-                        flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
-                    }
-
                 } else {
                     flags |= Document.FLAG_SUPPORTS_WRITE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
                 }
             }
 
+            if (isDir && shouldBlockDirectoryFromTree(docId)) {
+                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
+            }
+
             if (mimeType.startsWith("image/")) {
                 flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
             }
@@ -664,22 +658,36 @@
         return row;
     }
 
-    private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
-            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+    /**
+     * Some providers may want to restrict access to certain directories and files,
+     * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
+     * privacy reasons.
+     * Such providers should override this method.
+     */
+    protected boolean shouldHideDocument(@NonNull String documentId)
+            throws FileNotFoundException {
+        return false;
+    }
 
     /**
-     * In a scoped storage world, access to "Android/data" style directories are
-     * hidden for privacy reasons.
+     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
+     * a {@link String} {@code documentId}.
+     *
+     * @see #shouldHideDocument(String)
      */
-    protected boolean shouldHide(@NonNull File file) {
-        return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+    protected final boolean shouldHideDocument(@NonNull File document)
+            throws FileNotFoundException {
+        return shouldHideDocument(getDocIdForFile(document));
     }
 
-    private boolean shouldShow(@NonNull File file) {
-        return !shouldHide(file);
-    }
-
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
+    /**
+     * @return if the directory that should be blocked from being selected when the user launches
+     * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
+     */
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
         return false;
     }
 
diff --git a/core/java/com/android/internal/foldables/Android.bp b/core/java/com/android/internal/foldables/Android.bp
new file mode 100644
index 0000000..f1d06da
--- /dev/null
+++ b/core/java/com/android/internal/foldables/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+    name: "fold_lock_setting_flags",
+    package: "com.android.internal.foldables.flags",
+    srcs: [
+        "fold_lock_setting_flags.aconfig",
+    ],
+}
diff --git a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
index 4e3888a..a115ecf 100644
--- a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
+++ b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
@@ -17,16 +17,23 @@
 package com.android.internal.foldables;
 
 import android.content.res.Resources;
+import android.os.Build;
 import android.sysprop.FoldLockBehaviorProperties;
+import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.foldables.flags.Flags;
+
+import java.util.function.Supplier;
 
 /**
  * Wrapper class to access {@link FoldLockBehaviorProperties} and also assists with testing
  */
 public class FoldLockSettingAvailabilityProvider {
 
-    boolean mFoldLockBehaviorResourceValue;
+    private static final String TAG = "FoldLockSettingAvailabilityProvider";
+    private final boolean mFoldLockBehaviorResourceValue;
+    private final Supplier<Boolean> mFoldLockSettingEnabled = Flags::foldLockSettingEnabled;
 
     public FoldLockSettingAvailabilityProvider(Resources resources) {
         mFoldLockBehaviorResourceValue = resources.getBoolean(
@@ -35,6 +42,22 @@
 
     public boolean isFoldLockBehaviorAvailable() {
         return mFoldLockBehaviorResourceValue
-                && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false);
+                && flagOrSystemProperty();
+    }
+
+    private boolean flagOrSystemProperty() {
+        if ((Build.IS_ENG || Build.IS_USERDEBUG)
+                && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false)) {
+            return true;
+        }
+        try {
+            return mFoldLockSettingEnabled.get();
+        } catch (Throwable ex) {
+            Slog.i(TAG,
+                    "Flags not ready yet. Return false for "
+                            + Flags.FLAG_FOLD_LOCK_SETTING_ENABLED,
+                    ex);
+            return false;
+        }
     }
 }
diff --git a/core/java/com/android/internal/foldables/OWNERS b/core/java/com/android/internal/foldables/OWNERS
new file mode 100644
index 0000000..6ce1ee4
--- /dev/null
+++ b/core/java/com/android/internal/foldables/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/display/OWNERS
diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
new file mode 100644
index 0000000..44f436ea
--- /dev/null
+++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.foldables.flags"
+
+flag {
+    name: "fold_lock_setting_enabled"
+    namespace: "display_manager"
+    description: "Feature flag for Fold Lock Setting"
+    bug: "274447767"
+    is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index f77e962..65a2f4b 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -46,4 +46,5 @@
             in @nullable ImeTracker.Token statsToken);
     void onStylusHandwritingReady(int requestId, int pid);
     void resetStylusHandwriting(int requestId);
+    void switchKeyboardLayoutAsync(int direction);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 30ebbe2..792388d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -431,4 +431,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Calls {@link IInputMethodPrivilegedOperations#switchKeyboardLayoutAsync(int)}.
+     */
+    @AnyThread
+    public void switchKeyboardLayoutAsync(int direction) {
+        final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+        if (ops == null) {
+            return;
+        }
+        try {
+            ops.switchKeyboardLayoutAsync(direction);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 72a1bac..ca6c54d 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -24,6 +24,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
@@ -147,7 +148,8 @@
                 public void registerDisplayListener(DisplayManager.DisplayListener listener) {
                     manager.registerDisplayListener(listener, handler,
                             DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+                                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+                            ActivityThread.currentPackageName());
                 }
 
                 @Override
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e836e0..e6b036c 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -40,6 +40,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
@@ -57,6 +58,9 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
@@ -273,7 +277,13 @@
 
     public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
 
-    private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+    public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+    public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+    public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+    private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
@@ -366,6 +376,13 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
     }
 
     private static class InstanceHolder {
@@ -468,6 +485,10 @@
             CUJ_IME_INSETS_SHOW_ANIMATION,
             CUJ_IME_INSETS_HIDE_ANIMATION,
             CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
+            CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+            CUJ_PREDICTIVE_BACK_CROSS_TASK,
+            CUJ_PREDICTIVE_BACK_HOME,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -1101,6 +1122,14 @@
                 return "IME_INSETS_HIDE_ANIMATION";
             case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
                 return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+            case CUJ_LAUNCHER_UNFOLD_ANIM:
+                return "LAUNCHER_UNFOLD_ANIM";
+            case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+                return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+            case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+                return "PREDICTIVE_BACK_CROSS_TASK";
+            case CUJ_PREDICTIVE_BACK_HOME:
+                return "PREDICTIVE_BACK_HOME";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index d3103f1..7d78f29 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -37,7 +37,6 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -78,7 +77,7 @@
     private static final String TAG = "BatteryStatsHistory";
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    private static final int VERSION = 209;
+    private static final int VERSION = 210;
 
     private static final String HISTORY_DIR = "battery-history";
     private static final String FILE_SUFFIX = ".bh";
@@ -194,10 +193,11 @@
     private int mNextHistoryTagIdx = 0;
     private int mNumHistoryTagChars = 0;
     private int mHistoryBufferLastPos = -1;
-    private long mLastHistoryElapsedRealtimeMs = 0;
     private long mTrackRunningHistoryElapsedRealtimeMs = 0;
     private long mTrackRunningHistoryUptimeMs = 0;
-    private long mHistoryBaseTimeMs;
+    private final MonotonicClock mMonotonicClock;
+    // Monotonic time when we started writing to the history buffer
+    private long mHistoryBufferStartTime;
     private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
     private byte mLastHistoryStepLevel = 0;
     private boolean mMutable = true;
@@ -307,23 +307,26 @@
      * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
      */
     public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock) {
         this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
-                stepDetailsCalculator, clock, new TraceDelegate());
+                stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
         initHistoryBuffer();
     }
 
     @VisibleForTesting
     public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
             int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) {
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate tracer) {
         this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
-                clock, tracer, null);
+                clock, monotonicClock, tracer, null);
     }
 
     private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
             int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate tracer,
             BatteryStatsHistory writableHistory) {
         mHistoryBuffer = historyBuffer;
         mSystemDir = systemDir;
@@ -332,6 +335,7 @@
         mStepDetailsCalculator = stepDetailsCalculator;
         mTracer = tracer;
         mClock = clock;
+        mMonotonicClock = monotonicClock;
         mWritableHistory = writableHistory;
         if (mWritableHistory != null) {
             mMutable = false;
@@ -381,16 +385,18 @@
     }
 
     private BatteryHistoryFile makeBatteryHistoryFile() {
-        return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+        return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
     }
 
     public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock) {
         mMaxHistoryFiles = maxHistoryFiles;
         mMaxHistoryBufferSize = maxHistoryBufferSize;
         mStepDetailsCalculator = stepDetailsCalculator;
         mTracer = new TraceDelegate();
         mClock = clock;
+        mMonotonicClock = monotonicClock;
 
         mHistoryBuffer = Parcel.obtain();
         mSystemDir = null;
@@ -417,16 +423,16 @@
         mHistoryBuffer = Parcel.obtain();
         mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
 
+        mMonotonicClock = null;
         readFromParcel(parcel, true /* useBlobs */);
     }
 
     private void initHistoryBuffer() {
-        mHistoryBaseTimeMs = 0;
-        mLastHistoryElapsedRealtimeMs = 0;
         mTrackRunningHistoryElapsedRealtimeMs = 0;
         mTrackRunningHistoryUptimeMs = 0;
         mWrittenPowerStatsDescriptors.clear();
 
+        mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
         mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -466,7 +472,7 @@
             historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
 
             return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
-                    this);
+                    null, this);
         }
     }
 
@@ -491,7 +497,7 @@
      * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
      * create next history file.
      */
-    public void startNextFile() {
+    public void startNextFile(long elapsedRealtimeMs) {
         if (mMaxHistoryFiles == 0) {
             Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
             return;
@@ -517,6 +523,7 @@
             Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
         }
 
+        mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
         mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -596,6 +603,17 @@
     }
 
     /**
+     * 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;
+        }
+    }
+
+    /**
      * Start iterating history files and history buffer.
      *
      * @param startTimeMs monotonic time (the HistoryItem.time field) to start iterating from,
@@ -699,9 +717,12 @@
         if (mHistoryParcels != null) {
             while (mParcelIndex < mHistoryParcels.size()) {
                 final Parcel p = mHistoryParcels.get(mParcelIndex++);
-                if (!skipHead(p)) {
+                if (!verifyVersion(p)) {
                     continue;
                 }
+                // skip monotonic time field.
+                p.readLong();
+
                 final int bufSize = p.readInt();
                 final int curPos = p.dataPosition();
                 mCurrentParcelEnd = curPos + bufSize;
@@ -745,24 +766,36 @@
         }
         out.unmarshall(raw, 0, raw.length);
         out.setDataPosition(0);
-        return skipHead(out);
+        if (!verifyVersion(out)) {
+            return false;
+        }
+        // skip monotonic time field.
+        out.readLong();
+        return true;
     }
 
     /**
-     * Skip the header part of history parcel.
+     * Verify header part of history parcel.
      *
-     * @param p history parcel to skip head.
      * @return true if version match, false if not.
      */
-    private boolean skipHead(Parcel p) {
+    private boolean verifyVersion(Parcel p) {
         p.setDataPosition(0);
         final int version = p.readInt();
-        if (version != VERSION) {
-            return false;
-        }
-        // skip historyBaseTime field.
-        p.readLong();
-        return true;
+        return version == VERSION;
+    }
+
+    /**
+     * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
+     * buffer.
+     */
+    public long getHistoryBufferStartTime(Parcel p) {
+        int pos = p.dataPosition();
+        p.setDataPosition(0);
+        p.readInt();        // Skip the version field
+        long monotonicTime = p.readLong();
+        p.setDataPosition(pos);
+        return monotonicTime;
     }
 
     /**
@@ -1438,7 +1471,8 @@
             throw new ConcurrentModificationException("Battery history is not writable");
         }
 
-        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+        final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
+                                - mHistoryLastWritten.time;
         final int diffStates = mHistoryLastWritten.states ^ cur.states;
         final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
         final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
@@ -1476,7 +1510,9 @@
             mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
             mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
             mHistoryBufferLastPos = -1;
-            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+
+            elapsedRealtimeMs -= timeDiffMs;
+
             // If the last written history had a wakelock tag, we need to retain it.
             // Note that the condition above made sure that we aren't in a case where
             // both it and the current history item have a wakelock tag.
@@ -1513,7 +1549,7 @@
             HistoryItem copy = new HistoryItem();
             copy.setTo(cur);
 
-            startNextFile();
+            startNextFile(elapsedRealtimeMs);
 
             // startRecordingHistory will reset mHistoryCur.
             startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
@@ -1548,7 +1584,7 @@
         mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
         final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
-        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+        mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
         if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
             Slog.wtf(TAG, "Significantly earlier event written to battery history:"
                     + " time=" + mHistoryLastWritten.time
@@ -1556,7 +1592,6 @@
         }
         mHistoryLastWritten.tagsFirstOccurrence = hasTags;
         writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
-        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
         cur.wakelockTag = null;
         cur.wakeReasonTag = null;
         cur.eventCode = HistoryItem.EVENT_NONE;
@@ -1926,6 +1961,10 @@
             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();
@@ -1951,8 +1990,7 @@
             return;
         }
 
-        final long historyBaseTime = in.readLong();
-
+        mHistoryBufferStartTime = in.readLong();
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
 
@@ -1972,39 +2010,11 @@
             mHistoryBuffer.appendFrom(in, curPos, bufSize);
             in.setDataPosition(curPos + bufSize);
         }
-
-        mHistoryBaseTimeMs = historyBaseTime;
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** NEW mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-
-        if (mHistoryBaseTimeMs > 0) {
-            long elapsedRealtimeMs = mClock.elapsedRealtime();
-            mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-            mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
-            if (DEBUG) {
-                StringBuilder sb = new StringBuilder(128);
-                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
-                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-                Slog.i(TAG, sb.toString());
-            }
-        }
     }
 
     private void writeHistoryBuffer(Parcel out) {
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            sb.append(" mLastHistoryElapsedRealtimeMs: ");
-            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
         out.writeInt(BatteryStatsHistory.VERSION);
-        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+        out.writeLong(mHistoryBufferStartTime);
         out.writeInt(mHistoryBuffer.dataSize());
         if (DEBUG) {
             Slog.i(TAG, "***************** WRITING HISTORY: "
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index a5d2d0f..6bd5898 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.os;
 
-import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -34,8 +33,8 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistoryItr";
     private final BatteryStatsHistory mBatteryStatsHistory;
-    private final @CurrentTimeMillisLong long mStartTimeMs;
-    private final @CurrentTimeMillisLong long mEndTimeMs;
+    private final long mStartTimeMs;
+    private final long mEndTimeMs;
     private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
             new BatteryStats.HistoryStepDetails();
     private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
@@ -43,10 +42,10 @@
             new PowerStats.DescriptorRegistry();
     private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
     private boolean mNextItemReady;
+    private boolean mTimeInitialized;
 
-    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
-            @CurrentTimeMillisLong long startTimeMs,
-            @CurrentTimeMillisLong long endTimeMs) {
+    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
+            long endTimeMs) {
         mBatteryStatsHistory = history;
         mStartTimeMs = startTimeMs;
         mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
@@ -82,7 +81,12 @@
                 break;
             }
 
-            final long lastRealtimeMs = mHistoryItem.time;
+            if (!mTimeInitialized) {
+                mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+                mTimeInitialized = true;
+            }
+
+            final long lastMonotonicTimeMs = mHistoryItem.time;
             final long lastWalltimeMs = mHistoryItem.currentTime;
             try {
                 readHistoryDelta(p, mHistoryItem);
@@ -93,12 +97,13 @@
             if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
                     && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
                     && lastWalltimeMs != 0) {
-                mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+                mHistoryItem.currentTime =
+                        lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
             }
-            if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+            if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
                 break;
             }
-            if (mHistoryItem.currentTime >= mStartTimeMs) {
+            if (mHistoryItem.time >= mStartTimeMs) {
                 mNextItemReady = true;
                 return;
             }
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 5ea6ba8..1f44b33 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -191,6 +191,27 @@
     }
 
     /**
+     * Sets the new values for the given state.
+     */
+    public void setValues(int state, long[] values) {
+        if (state < 0 || state >= mStateCount) {
+            throw new IllegalArgumentException(
+                    "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+        }
+        if (values.length != mLength) {
+            throw new IllegalArgumentException(
+                    "Invalid array length: " + values.length + ", expected: " + mLength);
+        }
+        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+        if (container == null || container.mLength != values.length) {
+            container = new LongArrayContainer(values.length);
+        }
+        container.setValues(values);
+        native_setValues(mNativeObject, state, container.mNativeObject);
+        sTmpArrayContainer.set(container);
+    }
+
+    /**
      * Sets the new values.  The delta between the previously set values and these values
      * is distributed among the state according to the time the object spent in those states
      * since the previous call to updateValues.
@@ -317,6 +338,10 @@
     private static native void native_setState(long nativeObject, int state, long timestampMs);
 
     @CriticalNative
+    private static native void native_setValues(long nativeObject, int state,
+            long longArrayContainerNativeObject);
+
+    @CriticalNative
     private static native void native_updateValues(long nativeObject,
             long longArrayContainerNativeObject, long timestampMs);
 
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
new file mode 100644
index 0000000..6f114e3
--- /dev/null
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+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;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
+ * on reboot, but keeps going.
+ */
+public class MonotonicClock {
+    private static final String TAG = "MonotonicClock";
+
+    private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
+    private static final String XML_ATTR_TIMESHIFT = "timeshift";
+
+    private final AtomicFile mFile;
+    private final Clock mClock;
+    private long mTimeshift;
+
+    public MonotonicClock(File file) {
+        mFile = new AtomicFile(file);
+        mClock = Clock.SYSTEM_CLOCK;
+        read();
+    }
+
+    public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+        mClock = clock;
+        mFile = null;
+        mTimeshift = monotonicTime - mClock.elapsedRealtime();
+    }
+
+    /**
+     * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
+     * after a device reboot the time keeps increasing.
+     */
+    public long monotonicTime() {
+        return monotonicTime(mClock.elapsedRealtime());
+    }
+
+    /**
+     * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
+     * of being read from the Clock.
+     */
+    public long monotonicTime(long elapsedRealtimeMs) {
+        return mTimeshift + elapsedRealtimeMs;
+    }
+
+    private void read() {
+        if (!mFile.exists()) {
+            return;
+        }
+
+        try {
+            readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+        }
+    }
+
+    /**
+     * Saves the timeshift into a file.  Call this method just before system shutdown, after
+     * writing the last battery history event.
+     */
+    public void write() {
+        if (mFile == null) {
+            return;
+        }
+
+        mFile.write(out -> {
+            try {
+                writeXml(out, Xml.newBinarySerializer());
+                out.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+            }
+        });
+    }
+
+    /**
+     * Parses an XML file containing the persistent state of the monotonic clock.
+     */
+    @VisibleForTesting
+    public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+        long savedTimeshift = 0;
+        try {
+            parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                if (eventType == XmlPullParser.START_TAG
+                        && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
+                    savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
+                }
+                eventType = parser.next();
+            }
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        mTimeshift = 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 {
+        serializer.setOutput(out, StandardCharsets.UTF_8.name());
+        serializer.startDocument(null, true);
+        serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
+        serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
+        serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
+        serializer.endDocument();
+    }
+}
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index f971849..ecfed53 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -16,11 +16,21 @@
 
 package com.android.internal.os;
 
+import android.util.Slog;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Maintains multidimensional multi-state stats.  States could be something like on-battery (0,1),
@@ -29,22 +39,70 @@
  * values;
  */
 public class MultiStateStats {
+    private static final String TAG = "MultiStateStats";
+
+    private static final String XML_TAG_STATS = "stats";
+
     /**
      * A set of states, e.g. on-battery, screen-on, procstate.  The state values are integers
      * from 0 to States.mLabels.length
      */
     public static class States {
+        final String mName;
         final boolean mTracked;
         final String[] mLabels;
 
-        public States(boolean tracked, String... labels) {
-            this.mTracked = tracked;
-            this.mLabels = labels;
+        public States(String name, boolean tracked, String... labels) {
+            mName = name;
+            mTracked = tracked;
+            mLabels = labels;
         }
 
         public boolean isTracked() {
             return mTracked;
         }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String[] getLabels() {
+            return mLabels;
+        }
+
+        /**
+         * Iterates over all combinations of tracked states and invokes <code>consumer</code>
+         * for each of them.
+         */
+        public static void forEachTrackedStateCombination(States[] states,
+                Consumer<int[]> consumer) {
+            forEachTrackedStateCombination(consumer, states, new int[states.length], 0);
+        }
+
+        /**
+         * Recursive function that does a depth-first traversal of the multi-dimensional
+         * state space. Each time the traversal reaches the end of the <code>states</code> array,
+         * <code>statesValues</code> contains a unique combination of values for all tracked states.
+         * For untracked states, the corresponding values are left as 0.  The end result is
+         * that the <code>consumer</code> is invoked for every unique combination of tracked state
+         * values.  For example, it may be a sequence of calls like screen-on/power-on,
+         * screen-on/power-off, screen-off/power-on, screen-off/power-off.
+         */
+        private static void forEachTrackedStateCombination(Consumer<int[]> consumer,
+                States[] states, int[] statesValues, int stateIndex) {
+            if (stateIndex < statesValues.length) {
+                if (!states[stateIndex].mTracked) {
+                    forEachTrackedStateCombination(consumer, states, statesValues, stateIndex + 1);
+                    return;
+                }
+                for (int i = 0; i < states[stateIndex].mLabels.length; i++) {
+                    statesValues[stateIndex] = i;
+                    forEachTrackedStateCombination(consumer, states, statesValues, stateIndex + 1);
+                }
+                return;
+            }
+            consumer.accept(statesValues);
+        }
     }
 
     /**
@@ -155,11 +213,28 @@
                    >>> mStateBitFieldShifts[stateIndex];
         }
 
-        private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+        int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
             return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
                     | (value << mStateBitFieldShifts[stateIndex]);
         }
 
+        int setStateInComposite(int compositeState, String stateName, String stateLabel) {
+            for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+                States stateConfig = mStates[stateIndex];
+                if (stateConfig.mName.equals(stateName)) {
+                    for (int state = 0; state < stateConfig.mLabels.length; state++) {
+                        if (stateConfig.mLabels[state].equals(stateLabel)) {
+                            return setStateInComposite(compositeState, stateIndex, state);
+                        }
+                    }
+                    Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName);
+                    return -1;
+                }
+            }
+            Slog.e(TAG, "Unsupported state: " + stateName);
+            return -1;
+        }
+
         /**
          * Allocates a new stats container using this Factory's configuration.
          */
@@ -195,6 +270,10 @@
             }
             return serialState;
         }
+
+        int getSerialState(int compositeState) {
+            return mCompositeToSerialState[compositeState];
+        }
     }
 
     private final Factory mFactory;
@@ -241,6 +320,13 @@
     }
 
     /**
+     * Updates the stats values for the provided combination of states.
+     */
+    public void setStats(int[] states, long[] values) {
+        mCounter.setValues(mFactory.getSerialState(states), values);
+    }
+
+    /**
      * Resets the counters.
      */
     public void reset() {
@@ -254,27 +340,31 @@
     }
 
     /**
-     * Prints the accumulated stats, one line of every combination of states that has data.
+     * Stores contents in an XML doc.
      */
-    public void dump(PrintWriter pw) {
+    public void writeXml(TypedXmlSerializer serializer) throws IOException {
         long[] tmpArray = new long[mCounter.getArrayLength()];
-        dumpAllStates(pw, new int[mFactory.mStates.length], 0, tmpArray);
+
+        try {
+            States.forEachTrackedStateCombination(mFactory.mStates,
+                    states -> {
+                        try {
+                            writeXmlForStates(serializer, states, tmpArray);
+                        } catch (IOException e) {
+                            throw new RuntimeException(e);
+                        }
+                    });
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof IOException) {
+                throw (IOException) e.getCause();
+            } else {
+                throw e;
+            }
+        }
     }
 
-    private void dumpAllStates(PrintWriter pw, int[] states, int stateIndex, long[] values) {
-        if (stateIndex < states.length) {
-            if (!mFactory.mStates[stateIndex].mTracked) {
-                dumpAllStates(pw, states, stateIndex + 1, values);
-                return;
-            }
-
-            for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
-                states[stateIndex] = i;
-                dumpAllStates(pw, states, stateIndex + 1, values);
-            }
-            return;
-        }
-
+    private void writeXmlForStates(TypedXmlSerializer serializer, int[] states, long[] values)
+            throws IOException {
         mCounter.getCounts(values, mFactory.getSerialState(states));
         boolean nonZero = false;
         for (long value : values) {
@@ -287,17 +377,101 @@
             return;
         }
 
-        StringBuilder sb = new StringBuilder();
+        serializer.startTag(null, XML_TAG_STATS);
+
         for (int i = 0; i < states.length; i++) {
-            if (mFactory.mStates[i].mTracked) {
-                if (sb.length() != 0) {
-                    sb.append(" ");
-                }
-                sb.append(mFactory.mStates[i].mLabels[states[i]]);
+            if (mFactory.mStates[i].mTracked && states[i] != 0) {
+                serializer.attribute(null, mFactory.mStates[i].mName,
+                        mFactory.mStates[i].mLabels[states[i]]);
             }
         }
-        sb.append(" ");
-        sb.append(Arrays.toString(values));
-        pw.println(sb);
+        for (int i = 0; i < values.length; i++) {
+            if (values[i] != 0) {
+                serializer.attributeLong(null, "_" + i, values[i]);
+            }
+        }
+        serializer.endTag(null, XML_TAG_STATS);
+    }
+
+    /**
+     * Populates the object with contents in an XML doc. The parser is expected to be
+     * positioned on the opening tag of the corresponding element.
+     */
+    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        String outerTag = parser.getName();
+        long[] tmpArray = new long[mCounter.getArrayLength()];
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT
+               && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) {
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals(XML_TAG_STATS)) {
+                    Arrays.fill(tmpArray, 0);
+                    int compositeState = 0;
+                    int attributeCount = parser.getAttributeCount();
+                    for (int i = 0; i < attributeCount; i++) {
+                        String attributeName = parser.getAttributeName(i);
+                        if (attributeName.startsWith("_")) {
+                            int index;
+                            try {
+                                index = Integer.parseInt(attributeName.substring(1));
+                            } catch (NumberFormatException e) {
+                                throw new XmlPullParserException(
+                                        "Unexpected index syntax: " + attributeName, parser, e);
+                            }
+                            if (index < 0 || index >= tmpArray.length) {
+                                Slog.e(TAG, "State index out of bounds: " + index
+                                            + " length: " + tmpArray.length);
+                                return false;
+                            }
+                            tmpArray[index] = parser.getAttributeLong(i);
+                        } else {
+                            String attributeValue = parser.getAttributeValue(i);
+                            compositeState = mFactory.setStateInComposite(compositeState,
+                                    attributeName, attributeValue);
+                            if (compositeState == -1) {
+                                return false;
+                            }
+                        }
+                    }
+                    mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray);
+                }
+            }
+            eventType = parser.next();
+        }
+        return true;
+    }
+
+    /**
+     * Prints the accumulated stats, one line of every combination of states that has data.
+     */
+    public void dump(PrintWriter pw, Function<long[], String> statsFormatter) {
+        long[] values = new long[mCounter.getArrayLength()];
+        States.forEachTrackedStateCombination(mFactory.mStates, states -> {
+            mCounter.getCounts(values, mFactory.getSerialState(states));
+            boolean nonZero = false;
+            for (long value : values) {
+                if (value != 0) {
+                    nonZero = true;
+                    break;
+                }
+            }
+            if (!nonZero) {
+                return;
+            }
+
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < states.length; i++) {
+                if (mFactory.mStates[i].mTracked) {
+                    if (sb.length() != 0) {
+                        sb.append(" ");
+                    }
+                    sb.append(mFactory.mStates[i].mLabels[states[i]]);
+                }
+            }
+            sb.append(" ");
+            sb.append(statsFormatter.apply(values));
+            pw.println(sb);
+        });
     }
 }
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index e35b7f1..996e424 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -5,14 +5,10 @@
 per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
 
 # BatteryStats
-per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
 per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
-per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
-per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
 per-file *Kernel* = file:/BATTERY_STATS_OWNERS
+per-file *Clock* = file:/BATTERY_STATS_OWNERS
 per-file *MultiState* = file:/BATTERY_STATS_OWNERS
 per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
+per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
 
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 503e689..2298cbd 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -44,7 +44,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.Locale;
 
 /**
  * Reports power consumption values for various device activities. Reads values from an XML file.
@@ -295,7 +294,7 @@
 
     private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
 
-    private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3;
+    public static final int POWER_BRACKETS_UNSPECIFIED = -1;
 
     /**
      * A map from Power Use Item to its power consumption.
@@ -361,7 +360,7 @@
         }
         initCpuClusters();
         initCpuScalingPolicies();
-        initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER);
+        initCpuPowerBrackets();
         initDisplays();
         initModem();
     }
@@ -560,8 +559,7 @@
     /**
      * Parses or computes CPU power brackets: groups of states with similar power requirements.
      */
-    @VisibleForTesting
-    public void initCpuPowerBrackets(int defaultCpuPowerBracketNumber) {
+    private void initCpuPowerBrackets() {
         boolean anyBracketsSpecified = false;
         boolean allBracketsSpecified = true;
         for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
@@ -580,79 +578,32 @@
                     "Power brackets should be specified for all scaling policies or none");
         }
 
+        if (!allBracketsSpecified) {
+            mCpuPowerBracketCount = POWER_BRACKETS_UNSPECIFIED;
+            return;
+        }
+
         mCpuPowerBracketCount = 0;
-        if (allBracketsSpecified) {
-            for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
-                int policy = mCpuScalingPolicies.keyAt(i);
-                CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
-                final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy);
-                if (data.length != cpuScalingPolicyPower.powerBrackets.length) {
-                    throw new RuntimeException(
-                            "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy
-                                    + ", expected: "
-                                    + cpuScalingPolicyPower.powerBrackets.length);
-                }
-
-                for (int j = 0; j < data.length; j++) {
-                    final int bracket = (int) Math.round(data[j]);
-                    cpuScalingPolicyPower.powerBrackets[j] = bracket;
-                    if (bracket > mCpuPowerBracketCount) {
-                        mCpuPowerBracketCount = bracket;
-                    }
-                }
-            }
-            mCpuPowerBracketCount++;
-        } else {
-            double minPower = Double.MAX_VALUE;
-            double maxPower = Double.MIN_VALUE;
-            int stateCount = 0;
-            for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
-                int policy = mCpuScalingPolicies.keyAt(i);
-                CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
-                final int steps = cpuScalingPolicyPower.stepPower.length;
-                for (int step = 0; step < steps; step++) {
-                    final double power = getAveragePowerForCpuScalingStep(policy, step);
-                    if (power < minPower) {
-                        minPower = power;
-                    }
-                    if (power > maxPower) {
-                        maxPower = power;
-                    }
-                }
-                stateCount += steps;
+        for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+            int policy = mCpuScalingPolicies.keyAt(i);
+            CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+            final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy);
+            if (data.length != cpuScalingPolicyPower.powerBrackets.length) {
+                throw new RuntimeException(
+                        "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy
+                                + ", expected: "
+                                + cpuScalingPolicyPower.powerBrackets.length);
             }
 
-            if (stateCount <= defaultCpuPowerBracketNumber) {
-                mCpuPowerBracketCount = stateCount;
-                int bracket = 0;
-                for (int i = 0; i < mCpuScalingPolicies.size(); i++) {
-                    CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
-                    final int steps = cpuScalingPolicyPower.stepPower.length;
-                    for (int step = 0; step < steps; step++) {
-                        cpuScalingPolicyPower.powerBrackets[step] = bracket++;
-                    }
-                }
-            } else {
-                mCpuPowerBracketCount = defaultCpuPowerBracketNumber;
-                final double minLogPower = Math.log(minPower);
-                final double logBracket = (Math.log(maxPower) - minLogPower)
-                        / defaultCpuPowerBracketNumber;
-
-                for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
-                    int policy = mCpuScalingPolicies.keyAt(i);
-                    CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
-                    final int steps = cpuScalingPolicyPower.stepPower.length;
-                    for (int step = 0; step < steps; step++) {
-                        final double power = getAveragePowerForCpuScalingStep(policy, step);
-                        int bracket = (int) ((Math.log(power) - minLogPower) / logBracket);
-                        if (bracket >= defaultCpuPowerBracketNumber) {
-                            bracket = defaultCpuPowerBracketNumber - 1;
-                        }
-                        cpuScalingPolicyPower.powerBrackets[step] = bracket;
-                    }
+            for (int j = 0; j < data.length; j++) {
+                final int bracket = (int) Math.round(data[j]);
+                cpuScalingPolicyPower.powerBrackets[j] = bracket;
+                if (bracket > mCpuPowerBracketCount) {
+                    mCpuPowerBracketCount = bracket;
                 }
             }
         }
+        mCpuPowerBracketCount++;
     }
 
     private static class CpuScalingPolicyPower {
@@ -771,44 +722,13 @@
 
     /**
      * Returns the number of CPU power brackets: groups of states with similar power requirements.
+     * If power brackets are not specified, returns {@link #POWER_BRACKETS_UNSPECIFIED}
      */
     public int getCpuPowerBracketCount() {
         return mCpuPowerBracketCount;
     }
 
     /**
-     * Description of a CPU power bracket: which cluster/frequency combinations are included.
-     */
-    public String getCpuPowerBracketDescription(CpuScalingPolicies cpuScalingPolicies,
-            int powerBracket) {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < mCpuScalingPolicies.size(); i++) {
-            int policy = mCpuScalingPolicies.keyAt(i);
-            CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
-            int[] brackets = cpuScalingPolicyPower.powerBrackets;
-            int[] freqs = cpuScalingPolicies.getFrequencies(policy);
-            for (int step = 0; step < brackets.length; step++) {
-                if (brackets[step] == powerBracket) {
-                    if (sb.length() != 0) {
-                        sb.append(", ");
-                    }
-                    if (mCpuScalingPolicies.size() > 1) {
-                        sb.append(policy).append('/');
-                    }
-                    if (step < freqs.length) {
-                        sb.append(freqs[step] / 1000);
-                    }
-                    sb.append('(');
-                    sb.append(String.format(Locale.US, "%.1f",
-                            getAveragePowerForCpuScalingStep(policy, step)));
-                    sb.append(')');
-                }
-            }
-        }
-        return sb.toString();
-    }
-
-    /**
      * Returns the CPU power bracket corresponding to the specified scaling policy and frequency
      * step
      */
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 8f66d1f..1a7efac 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -24,9 +24,16 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -48,12 +55,12 @@
     private static final int STATS_ARRAY_LENGTH_SHIFT =
             Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
     public static final int MAX_STATS_ARRAY_LENGTH =
-            2 ^ Integer.bitCount(STATS_ARRAY_LENGTH_MASK) - 1;
+            (1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
     private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
     private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
             Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
     public static final int MAX_UID_STATS_ARRAY_LENGTH =
-            (2 ^ Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
+            (1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
 
     /**
      * Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
@@ -61,6 +68,13 @@
      * to adjust the algorithm in accordance with the stats available on the device.
      */
     public static class Descriptor {
+        public static final String XML_TAG_DESCRIPTOR = "descriptor";
+        private static final String XML_ATTR_ID = "id";
+        private static final String XML_ATTR_NAME = "name";
+        private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+        private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
+        private static final String XML_TAG_EXTRAS = "extras";
+
         /**
          * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
          * to; or a custom power component ID (if the value
@@ -126,7 +140,7 @@
             int firstWord = parcel.readInt();
             int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
             if (version != PARCEL_FORMAT_VERSION) {
-                Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+                Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
                            + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
                 return null;
             }
@@ -155,6 +169,71 @@
                     that.extras);  // Since the Parcel is now unparceled, do a deep comparison
         }
 
+        /**
+         * Stores contents in an XML doc.
+         */
+        public void writeXml(TypedXmlSerializer serializer) throws IOException {
+            serializer.startTag(null, XML_TAG_DESCRIPTOR);
+            serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+            serializer.attribute(null, XML_ATTR_NAME, name);
+            serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+            serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+            try {
+                serializer.startTag(null, XML_TAG_EXTRAS);
+                extras.saveToXml(serializer);
+                serializer.endTag(null, XML_TAG_EXTRAS);
+            } catch (XmlPullParserException e) {
+                throw new IOException(e);
+            }
+            serializer.endTag(null, XML_TAG_DESCRIPTOR);
+        }
+
+        /**
+         * Creates a Descriptor by parsing an XML doc.  The parser is expected to be positioned
+         * on or before the opening "descriptor" tag.
+         */
+        public static Descriptor createFromXml(TypedXmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int powerComponentId = -1;
+            String name = null;
+            int statsArrayLength = 0;
+            int uidStatsArrayLength = 0;
+            PersistableBundle extras = null;
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
+                if (eventType == XmlPullParser.START_TAG) {
+                    switch (parser.getName()) {
+                        case XML_TAG_DESCRIPTOR:
+                            powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
+                            name = parser.getAttributeValue(null, XML_ATTR_NAME);
+                            statsArrayLength = parser.getAttributeInt(null,
+                                    XML_ATTR_STATS_ARRAY_LENGTH);
+                            uidStatsArrayLength = parser.getAttributeInt(null,
+                                    XML_ATTR_UID_STATS_ARRAY_LENGTH);
+                            break;
+                        case XML_TAG_EXTRAS:
+                            extras = PersistableBundle.restoreFromXml(parser);
+                            break;
+                    }
+                }
+                eventType = parser.next();
+            }
+            if (powerComponentId == -1) {
+                return null;
+            } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+                return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
+                        extras);
+            } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
+                return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength,
+                        extras);
+            } else {
+                Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
+                return null;
+            }
+        }
+
         @Override
         public int hashCode() {
             return Objects.hash(powerComponentId);
@@ -259,7 +338,7 @@
 
             Descriptor descriptor = registry.get(powerComponentId);
             if (descriptor == null) {
-                Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+                Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
                 return null;
             }
             PowerStats stats = new PowerStats(descriptor);
@@ -274,7 +353,7 @@
                 stats.uidStats.put(uid, uidStats);
             }
             if (parcel.dataPosition() != endPos) {
-                Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+                Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
                            + ", actual length: " + (parcel.dataPosition() - startPos));
                 return null;
             }
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
index 6fd5018..504928c 100644
--- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -74,16 +74,15 @@
         return windowSwipeToDismiss;
     }
 
-    private boolean isPointerIndexValid(MotionEvent ev) {
+    private int getIndexForValidPointer(MotionEvent ev) {
         int pointerIndex = ev.findPointerIndex(mActivePointerId);
         if (pointerIndex == -1) {
             if (DEBUG) {
                 Log.e(TAG, "Invalid pointer index: ignoring.");
             }
             mDiscardIntercept = true;
-            return false;
         }
-        return true;
+        return pointerIndex;
     }
 
     private void updateSwiping(MotionEvent ev) {
@@ -98,7 +97,7 @@
         }
     }
 
-    private void updateDiscardIntercept(MotionEvent ev) {
+    private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) {
         if (!mSwiping) {
             // Don't look at canScroll until we have passed the touch slop
             return;
@@ -107,8 +106,8 @@
             return;
         }
         final boolean checkLeft = mDownX < ev.getRawX();
-        final float x = ev.getX(mActivePointerId);
-        final float y = ev.getY(mActivePointerId);
+        final float x = ev.getX(pointerIndex);
+        final float y = ev.getY(pointerIndex);
         if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
             mDiscardIntercept = true;
         }
@@ -152,11 +151,12 @@
                 if (mDiscardIntercept) {
                     break;
                 }
-                if (!isPointerIndexValid(ev)) {
+                final int pointerIndex = getIndexForValidPointer(ev);
+                if (pointerIndex == -1) {
                     break;
                 }
                 updateSwiping(ev);
-                updateDiscardIntercept(ev);
+                updateDiscardIntercept(ev, pointerIndex);
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ec525f0..4bb7c33 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -91,6 +91,8 @@
     WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             "CoreBackPreview"),
     WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
+
+    WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 4065055..8236783 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -108,4 +108,5 @@
     boolean removeWeakEscrowToken(long handle, int userId);
     boolean isWeakEscrowTokenActive(long handle, int userId);
     boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
+    void unlockUserKeyIfUnsecured(int userId);
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5b8f62..8d11672 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1933,8 +1933,29 @@
         }
     }
 
+    /**
+     * If the user is not secured, ie doesn't have an LSKF, then decrypt the user's synthetic
+     * password and use it to unlock various cryptographic keys associated with the user.  This
+     * primarily includes unlocking the user's credential-encrypted (CE) storage.  It also includes
+     * deriving or decrypting the vendor auth secret and sending it to the AuthSecret HAL.
+     * <p>
+     * These tasks would normally be done when the LSKF is verified.  This method is where these
+     * tasks are done when the user doesn't have an LSKF.  It's called when the user is started.
+     * <p>
+     * Except on permission denied, this method doesn't throw an exception on failure.  However, the
+     * last thing that it does is unlock CE storage, and whether CE storage has been successfully
+     * unlocked can be determined by {@link StorageManager#isCeStorageUnlocked()}.
+     * <p>
+     * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @param userId the ID of the user whose keys to unlock
+     */
     public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
-        getLockSettingsInternal().unlockUserKeyIfUnsecured(userId);
+        try {
+            getLockSettings().unlockUserKeyIfUnsecured(userId);
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+        }
     }
 
     public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 6063c90..8114e1f 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -60,17 +60,6 @@
     public abstract void onThirdPartyAppsStarted();
 
     /**
-     * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
-     * doesn't have an LSKF.
-     * <p>
-     * This doesn't throw an exception on failure; whether the storage has been unlocked can be
-     * determined by {@link StorageManager#isUserKeyUnlocked()}.
-     *
-     * @param userId the ID of the user whose storage to unlock
-     */
-    public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId);
-
-    /**
      * Creates the locksettings state for a new user.
      * <p>
      * This includes creating a synthetic password and protecting it with an empty LSKF.
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d70d3..50253cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1315,7 +1315,16 @@
         ALOGI("VM exiting with result code %d.", code);
         onExit(code);
     }
+
+#ifdef __ANDROID_CLANG_COVERAGE__
+    // When compiled with coverage, a function is registered with atexit to call
+    // `__llvm_profile_write_file` when the process exit.
+    // For Clang code coverage to work, call exit instead of _exit to run hooks
+    // registered with atexit.
+    ::exit(code);
+#else
     ::_exit(code);
+#endif
 }
 
 void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 34ef7b3..5b95ee7 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -85,6 +85,18 @@
     return false;
 }
 
+static jlong android_hardware_OverlayProperties_createDefault(JNIEnv* env, jobject thiz) {
+    gui::OverlayProperties* overlayProperties = new gui::OverlayProperties;
+    gui::OverlayProperties::SupportedBufferCombinations combination;
+    combination.pixelFormats = {HAL_PIXEL_FORMAT_RGBA_8888};
+    combination.standards = {HAL_DATASPACE_BT709};
+    combination.transfers = {HAL_DATASPACE_TRANSFER_SRGB};
+    combination.ranges = {HAL_DATASPACE_RANGE_FULL};
+    overlayProperties->combinations.emplace_back(combination);
+    overlayProperties->supportMixedColorSpaces = true;
+    return reinterpret_cast<jlong>(overlayProperties);
+}
+
 // ----------------------------------------------------------------------------
 // Serialization
 // ----------------------------------------------------------------------------
@@ -150,6 +162,7 @@
             (void*) android_hardware_OverlayProperties_write },
     { "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
             (void*) android_hardware_OverlayProperties_read },
+    {"nCreateDefault", "()J", (void*) android_hardware_OverlayProperties_createDefault },
 };
 // clang-format on
 
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index deb138f..9c883d1 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -265,6 +265,18 @@
     return mgr->isDataInjectionEnabled();
 }
 
+static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+                                                   jlong sensorManager) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    return mgr->isReplayDataInjectionEnabled();
+}
+
+static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+                                                            jlong sensorManager) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    return mgr->isHalBypassReplayDataInjectionEnabled();
+}
+
 static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
                                       jint deviceId, jlong size, jint channelType, jint fd,
                                       jobject hardwareBufferObj) {
@@ -533,6 +545,11 @@
 
         {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
+        {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled},
+
+        {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z",
+         (void *)nativeIsHalBypassReplayDataInjectionEnabled},
+
         {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
          (void *)nativeCreateDirectChannel},
 
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index f97d41b..262f5e8 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -42,13 +42,6 @@
         return NULL;
     }
 
-    // b/274058082: Pass a copy of the key character map to avoid concurrent
-    // access
-    std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
-    if (map != nullptr) {
-        map = std::make_shared<KeyCharacterMap>(*map);
-    }
-
     ScopedLocalRef<jstring> descriptorObj(env,
             env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str()));
     if (!descriptorObj.get()) {
@@ -67,9 +60,14 @@
                                                                   ? layoutInfo->layoutType.c_str()
                                                                   : NULL));
 
+    std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
+    std::unique_ptr<KeyCharacterMap> mapCopy;
+    if (map != nullptr) {
+        mapCopy = std::make_unique<KeyCharacterMap>(*map);
+    }
     ScopedLocalRef<jobject> kcmObj(env,
                                    android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
-                                                                       map));
+                                                                       std::move(mapCopy)));
     if (!kcmObj.get()) {
         return NULL;
     }
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 7f69e22..a79e37a 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -48,7 +48,7 @@
 
 class NativeKeyCharacterMap {
 public:
-    NativeKeyCharacterMap(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map)
+    NativeKeyCharacterMap(int32_t deviceId, std::unique_ptr<KeyCharacterMap> map)
           : mDeviceId(deviceId), mMap(std::move(map)) {}
 
     ~NativeKeyCharacterMap() {
@@ -58,16 +58,18 @@
         return mDeviceId;
     }
 
-    inline const std::shared_ptr<KeyCharacterMap> getMap() const { return mMap; }
+    inline const std::unique_ptr<KeyCharacterMap>& getMap() const {
+        return mMap;
+    }
 
 private:
     int32_t mDeviceId;
-    std::shared_ptr<KeyCharacterMap> mMap;
+    std::unique_ptr<KeyCharacterMap> mMap;
 };
 
 jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
-                                            const std::shared_ptr<KeyCharacterMap> kcm) {
-    NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, kcm);
+                                            std::unique_ptr<KeyCharacterMap> kcm) {
+    NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, std::move(kcm));
     if (!nativeMap) {
         return nullptr;
     }
@@ -91,7 +93,7 @@
         return 0;
     }
 
-    std::shared_ptr<KeyCharacterMap> kcm = nullptr;
+    std::unique_ptr<KeyCharacterMap> kcm;
     // Check if map is a null character map
     if (parcel->readBool()) {
         kcm = KeyCharacterMap::readFromParcel(parcel);
@@ -99,7 +101,7 @@
             return 0;
         }
     }
-    NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, kcm);
+    NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, std::move(kcm));
     return reinterpret_cast<jlong>(map);
 }
 
@@ -230,9 +232,9 @@
 }
 
 static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) {
-    const std::shared_ptr<KeyCharacterMap>& map1 =
+    const std::unique_ptr<KeyCharacterMap>& map1 =
             (reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap();
-    const std::shared_ptr<KeyCharacterMap>& map2 =
+    const std::unique_ptr<KeyCharacterMap>& map2 =
             (reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap();
     if (map1 == nullptr || map2 == nullptr) {
         return map1 == map2;
diff --git a/core/jni/android_view_KeyCharacterMap.h b/core/jni/android_view_KeyCharacterMap.h
index be03353..a8aabd1 100644
--- a/core/jni/android_view_KeyCharacterMap.h
+++ b/core/jni/android_view_KeyCharacterMap.h
@@ -25,7 +25,7 @@
 
 /* Creates a KeyCharacterMap object from the given information. */
 extern jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
-                                                   const std::shared_ptr<KeyCharacterMap> kcm);
+                                                   std::unique_ptr<KeyCharacterMap> kcm);
 
 } // namespace android
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f79dbe7..9833598 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,8 @@
     jfieldID height;
     jfieldID xDpi;
     jfieldID yDpi;
-    jfieldID refreshRate;
+    jfieldID peakRefreshRate;
+    jfieldID vsyncRate;
     jfieldID appVsyncOffsetNanos;
     jfieldID presentationDeadlineNanos;
     jfieldID group;
@@ -962,10 +963,11 @@
 }
 
 static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj,
-                                       jlong nativeObject, jint category) {
+                                       jlong nativeObject, jint category,
+                                       jboolean smoothSwitchOnly) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
-    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
+    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);
 }
 
 static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -1230,7 +1232,8 @@
     env->SetFloatField(object, gDisplayModeClassInfo.xDpi, config.xDpi);
     env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi);
 
-    env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate);
+    env->SetFloatField(object, gDisplayModeClassInfo.peakRefreshRate, config.peakRefreshRate);
+    env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate);
     env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset);
     env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
                       config.presentationDeadline);
@@ -2179,7 +2182,7 @@
             (void*)nativeSetFrameRate },
     {"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
             (void*)nativeSetDefaultFrameRateCompatibility},
-    {"nativeSetFrameRateCategory", "(JJI)V",
+    {"nativeSetFrameRateCategory", "(JJIZ)V",
             (void*)nativeSetFrameRateCategory},
     {"nativeSetFrameRateSelectionStrategy", "(JJI)V",
             (void*)nativeSetFrameRateSelectionStrategy},
@@ -2393,7 +2396,8 @@
     gDisplayModeClassInfo.height = GetFieldIDOrDie(env, modeClazz, "height", "I");
     gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F");
     gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F");
-    gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F");
+    gDisplayModeClassInfo.peakRefreshRate = GetFieldIDOrDie(env, modeClazz, "peakRefreshRate", "F");
+    gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F");
     gDisplayModeClassInfo.appVsyncOffsetNanos =
             GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J");
     gDisplayModeClassInfo.presentationDeadlineNanos =
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 6920211..1f29735 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -55,6 +55,15 @@
     counter->setState(state, timestamp);
 }
 
+static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
+    battery::LongArrayMultiStateCounter *counter =
+            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    std::vector<uint64_t> *vector =
+            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+    counter->setValue(state, *vector);
+}
+
 static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
                                 jlong timestamp) {
     battery::LongArrayMultiStateCounter *counter =
@@ -210,6 +219,8 @@
         // @CriticalNative
         {"native_setState", "(JIJ)V", (void *)native_setState},
         // @CriticalNative
+        {"native_setValues", "(JIJ)V", (void *)native_setValues},
+        // @CriticalNative
         {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
         // @CriticalNative
         {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
new file mode 100644
index 0000000..8c33041
--- /dev/null
+++ b/core/proto/android/app/appstartinfo.proto
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/proto_logging/stats/enums/app/enums.proto";
+
+/**
+ * An android.app.ApplicationStartInfo object.
+ */
+message ApplicationStartInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int32 pid = 1;
+    optional int32 real_uid = 2;
+    optional int32 package_uid = 3;
+    optional int32 defining_uid = 4;
+    optional string process_name = 5;
+    optional AppStartStartupState startup_state = 6;
+    optional AppStartReasonCode reason = 7;
+    optional bytes startup_timestamps = 8;
+    optional AppStartStartType start_type = 9;
+    optional bytes start_intent = 10;
+    optional AppStartLaunchMode launch_mode = 11;
+}
diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c91..0000000
--- a/core/proto/android/nfc/Android.bp
+++ /dev/null
@@ -1,43 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
-    name: "srcs_nfc_proto",
-    srcs: [
-        "*.proto",
-    ],
-}
-
-// Will be statically linked by `framework-nfc`.
-java_library {
-    name: "nfc-proto-java-gen",
-    installable: false,
-    proto: {
-        type: "stream",
-        include_dirs: [
-            "external/protobuf/src",
-        ],
-    },
-    srcs: [
-        ":srcs_nfc_proto",
-    ],
-    sdk_version: "current",
-    min_sdk_version: "current",
-}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4732702..5b0a502 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -99,6 +99,7 @@
         optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_magnification_gesture = 53 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto accessibility_magnification_two_finger_triple_tap_enabled = 54 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Accessibility accessibility = 2;
 
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 025a57d..c5889ba 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -20,6 +20,7 @@
 
 import "frameworks/base/core/proto/android/app/activitymanager.proto";
 import "frameworks/base/core/proto/android/app/appexitinfo.proto";
+import "frameworks/base/core/proto/android/app/appstartinfo.proto";
 import "frameworks/base/core/proto/android/app/notification.proto";
 import "frameworks/base/core/proto/android/app/profilerinfo.proto";
 import "frameworks/base/core/proto/android/content/component_name.proto";
@@ -1041,3 +1042,23 @@
     }
     repeated Package packages = 2;
 }
+
+// sync with com.android.server.am.am.ProcessList.java
+message AppsStartInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 last_update_timestamp = 1;
+    message Package {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string package_name = 1;
+        message User {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+            optional int32 uid = 1;
+            repeated .android.app.ApplicationStartInfoProto app_start_info = 2;
+        }
+        repeated User users = 2;
+    }
+    repeated Package packages = 2;
+}
diff --git a/core/proto/android/service/OWNERS b/core/proto/android/service/OWNERS
index 70cb50f..7a19155 100644
--- a/core/proto/android/service/OWNERS
+++ b/core/proto/android/service/OWNERS
@@ -1 +1,2 @@
 per-file sensor_service.proto = arthuri@google.com, bduddie@google.com, stange@google.com
+per-file package.proto = file:platform/frameworks/base:/PACKAGE_MANAGER_OWNERS
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index eb14db0..068f4dd 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -115,6 +115,9 @@
 
                 // Only set if the app defined a monochrome icon.
                 optional string monochrome_icon_bitmap_path = 3;
+
+                // The component name of the original activity (pre-archival).
+                optional string original_component_name = 4;
             }
 
             /** Information about main activities. */
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b71995f..4e686b7 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -104,6 +104,7 @@
 
 android_app {
     name: "framework-res",
+    use_resource_processor: false,
     sdk_version: "core_platform",
     certificate: "platform",
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 298eb2f..8c91be8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
     <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTOPPED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION" />
@@ -2107,12 +2108,12 @@
         android:protectionLevel="signature" />
 
     <!-- Allows direct access to the <RemoteAuth>Service interfaces.
-         @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+         @hide -->
     <permission android:name="android.permission.MANAGE_REMOTE_AUTH"
                 android:protectionLevel="signature" />
 
     <!-- Allows direct access to the <RemoteAuth>Service authentication methods.
-         @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+         @hide -->
     <permission android:name="android.permission.USE_REMOTE_AUTH"
                 android:protectionLevel="signature" />
 
@@ -4564,6 +4565,12 @@
     <permission android:name="android.permission.SET_DEBUG_APP"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- Allows an application to access the data in Dropbox.
+    <p>Not for use by third-party applications.
+    @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") -->
+    <permission android:name="android.permission.READ_DROPBOX_DATA"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- Allows an application to set the maximum number of (not needed)
          application processes that can be running.
          <p>Not for use by third-party applications. -->
@@ -4646,7 +4653,7 @@
         @hide
         @SystemApi -->
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows an application to bind to third party quick settings tiles.
          <p>Should only be requested by the System, should be required by
@@ -4710,7 +4717,7 @@
          @hide
     -->
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
-        android:protectionLevel="signature|module" />
+        android:protectionLevel="signature|module|recents" />
 
     <!-- Allows an application to avoid all toast rate limiting restrictions.
          <p>Not for use by third-party applications.
@@ -5667,7 +5674,8 @@
                 android:description="@string/permdesc_deliverCompanionMessages"
                 android:protectionLevel="normal" />
 
-    <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+    <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
+         @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
          Allows an application to send and receive messages via CDM transports.
     -->
     <permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
@@ -6121,7 +6129,9 @@
         android:protectionLevel="signature|privileged|development|appop|retailDemo" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
-    <!-- @SystemApi @hide Allows trusted system components to report events to UsageStatsManager -->
+    <!-- @SystemApi @hide
+         @FlaggedApi("android.app.usage.report_usage_stats_permission")
+         Allows trusted system components to report events to UsageStatsManager -->
     <permission android:name="android.permission.REPORT_USAGE_STATS"
                 android:protectionLevel="signature|module" />
 
@@ -7140,7 +7150,7 @@
          {@link ActivityOptions#makeRemoteAnimation}
          @hide <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- Allows an application to watch changes and/or active state of app ops.
          @hide <p>Not for use by third-party applications. -->
@@ -7232,13 +7242,25 @@
                 android:description="@string/permdesc_fullScreenIntent"
                 android:protectionLevel="normal|appop" />
 
-    <!-- Required for the assistant apps targeting {@link android.os.Build.VERSION_CODES#V}
-         that receive voice trigger from the trusted hotword detection service.
+    <!-- @SystemApi Required for the privileged assistant apps targeting
+         {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+         that receive voice trigger from a sandboxed {@link HotwordDetectionService}.
          <p>Protection level: signature|privileged|appop
+         @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
          @hide -->
     <permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
                 android:protectionLevel="signature|privileged|appop" />
 
+    <!-- @SystemApi Required for the privileged assistant apps targeting
+         {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+         that receive training data from a sandboxed {@link HotwordDetectionService} or
+         {@link VisualQueryDetectionService}.
+         <p>Protection level: internal|appop
+         @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
+         @hide -->
+    <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
+                android:protectionLevel="internal|appop" />
+
     <!-- @SystemApi Allows requesting the framework broadcast the
          {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
          @hide -->
@@ -7752,6 +7774,14 @@
     <permission android:name="android.permission.MANAGE_DISPLAYS"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows apps to reset hotword training data egress count for testing.
+     <p>CTS tests will use UiAutomation.AdoptShellPermissionIdentity() to gain access.
+     <p>Protection level: signature
+     @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds")
+     @hide -->
+    <permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT"
+                android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml
new file mode 100644
index 0000000..dddad81
--- /dev/null
+++ b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="20"
+        android:viewportHeight="20">
+    <path
+        android:pathData="M15.333,5.333V4.667C15.333,4.3 15.033,4 14.667,4L13.333,4C12.967,4 12.667,4.3 12.667,4.667V5.333H12V8C12,8.367 12.3,8.667 12.667,8.667H13.333L13.333,13.333C13.333,14.067 12.733,14.667 12,14.667C11.267,14.667 10.667,14.067 10.667,13.333L10.667,11.333V6.667C10.667,5.193 9.473,4 8,4C6.527,4 5.333,5.193 5.333,6.667L5.333,11.333H4.667C4.3,11.333 4,11.633 4,12L4,14.667H4.667V15.333C4.667,15.7 4.967,16 5.333,16H6.667C7.033,16 7.333,15.7 7.333,15.333V14.667H8L8,12C8,11.633 7.7,11.333 7.333,11.333H6.667L6.667,6.667C6.667,5.933 7.267,5.333 8,5.333C8.733,5.333 9.333,5.933 9.333,6.667V11.333L9.333,13.333C9.333,14.807 10.527,16 12,16C13.473,16 14.667,14.807 14.667,13.333L14.667,8.667H15.333C15.7,8.667 16,8.367 16,8V5.333H15.333Z"
+        android:fillColor="#FFFFFFFF"
+        android:fillType="evenOdd"/>
+</vector>
+
+
diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml
index 66dd392..b664fe4f 100644
--- a/core/res/res/drawable-watch/ic_lock_bugreport.xml
+++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml
@@ -20,12 +20,12 @@
         android:viewportHeight="24.0"
         android:tint="@android:color/white">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M10,14h4v2h-4z"/>
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M10,10h4v2h-4z"/>
 </vector>
diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml
index 34bc88c..b437a4b 100644
--- a/core/res/res/drawable-watch/ic_lock_power_off.xml
+++ b/core/res/res/drawable-watch/ic_lock_power_off.xml
@@ -21,6 +21,6 @@
         android:viewportHeight="24"
         android:tint="@android:color/white">
     <path
-        android:fillColor="@android:color/black"
+        android:fillColor="@android:color/white"
         android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
 </vector>
diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml
index 24d7c34..52933aa 100644
--- a/core/res/res/drawable-watch/ic_restart.xml
+++ b/core/res/res/drawable-watch/ic_restart.xml
@@ -21,6 +21,6 @@
         android:viewportHeight="24"
         android:tint="@android:color/white">
     <path
-        android:fillColor="@android:color/black"
+        android:fillColor="@android:color/white"
         android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/>
 </vector>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index cd951cb..5037239 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programme wat batterykrag gebruik"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toeganklikheidgebruik"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Skerm"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans batterykrag"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> programme gebruik tans batterykrag"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik vir besonderhede oor battery- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Laat ’n metgeselapp toe om voorgronddienste van agtergrond af te begin"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoon is beskikbaar"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoon is geblokkeer"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan nie na skerm weerspieël nie"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik ’n ander kabel en probeer weer"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel steun dalk nie skerms nie"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Jou USB-C-kabel koppel dalk nie behoorlik aan skerms nie"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is aan"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans albei skerms om inhoud te wys"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 42ba598..9a8bb62 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ባትሪ በመፍጀት ላይ ያሉ መተግበሪያዎች"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ማጉላት"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"የተደራሽነት አጠቃቀም"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ማሳያ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ባትሪ እየተጠቀመ ነው"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> መተግበሪያዎች ባትሪ እየተጠቀሙ ነው"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"በባትሪ እና ውሂብ አጠቃቀም ላይ ዝርዝሮችን ለማግኘት መታ ያድርጉ"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"አጃቢ መተግበሪያ ከዳራ የፊት አገልግሎቶችን እንዲጀምር ያስችላል።"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"ማይክሮፎን ይገኛል"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ማይክሮፎን ታግዷል"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ወደ ማሳያ ማንጸባረቅ አልተቻለም"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"የተለየ ገመድ ይጠቀሙ እና እንደገና ይሞክሩ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ገመድ ማሳያዎችን ላይደግፍ ይችላል"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"የእርስዎ USB-C ገመድ ከማሳያዎች ጋር በትክክል ላይገናኝ ይችላል"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ገፅ በርቷል"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ይዘትን ለማሳየት ሁለቱንም ማሳያዎች እየተጠቀመ ነው"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 68d7ff4..f6d7c64 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -294,6 +294,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"التطبيقات التي تستهلك البطارية"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"التكبير"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"استخدام \"أدوات تسهيل الاستخدام\""</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"الشاشة"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"يستخدم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> البطارية"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"تستخدم <xliff:g id="NUMBER">%1$d</xliff:g> من التطبيقات البطارية"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"انقر للحصول على تفاصيل حول البطارية واستخدام البيانات"</string>
@@ -2337,6 +2338,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"يسمح هذا الإذن للتطبيق المصاحب ببدء الخدمات التي تعمل في المقدّمة من الخلفية."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"الميكروفون متاح."</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"تم حظر الميكروفون."</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"يتعذّر إجراء نسخ مطابق لمحتوى جهازك إلى الشاشة"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"يُرجى استخدام كابل آخر وإعادة المحاولة."</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"قد لا يتوافق الكابل مع الشاشات"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏قد لا يتم توصيل الكابل المزوَّد بمنفذ USB-C بالشاشات بشكل صحيح."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‏ميزة Dual Screen مفعّلة"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"يستخدم \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" كلتا الشاشتين لعرض المحتوى."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index ba3e756..df4f28a 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"বেটাৰী খৰচ কৰা এপ্‌সমূহ"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বিবৰ্ধন"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"সাধ্য সুবিধাৰ ব্যৱহাৰ"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ডিছপ্লে’"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টা এপে বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"বেটাৰী আৰু ডেটাৰ ব্যৱহাৰৰ বিষয়ে সবিশেষ জানিবলৈ টিপক"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"এটা সহযোগী এপক নেপথ্যৰ পৰা অগ্ৰভূমি সেৱাসমূহ আৰম্ভ কৰিবলৈ দিয়ে।"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্ৰ’ফ’নটো উপলব্ধ"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্ৰ’ফ’নটো অৱৰোধ কৰি থোৱা আছে"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"সংযুক্ত ডিছপ্লে’ উপলব্ধ নহয়"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য এডাল কে’বল ব্যৱহাৰ কৰি পুনৰ চেষ্টা কৰক"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কে’বলে ডিছপ্লে’ সমৰ্থন নকৰিবও পাৰে"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপোনাৰ USB-C কে’বল ডিছপ্লে’ৰ সৈতে সঠিকভাৱে সংযোগ নহ’বও পাৰে"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen অন আছে"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ সমল দেখুৱাবলৈ দুয়োখন ডিছপ্লে’ ব্যৱহাৰ কৰি আছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 4124dfa..8bfd8b5 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareyadan istifadə edən tətbiqlər"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Böyütmə"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Əlçatımlılıq istifadəsi"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> batareyadan istifadə edir"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> tətbiq batareyadan istifadə edir"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya və data istifadəsi haqqında ətraflı məlumat üçün klikləyin"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Kompanyon tətbiqinə ön fon xidmətlərini arxa fondan başlatmaq icazəsi verir."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon əlçatandır"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon blok edilib"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeydə əks etdirmək olmur"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Başqa kabel istifadə edin və yenidən cəhd edin"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeyləri dəstəkləməyə bilər"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabeli displeylərə düzgün qoşulmaya bilər"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"İkili ekran"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"İkili ekran aktivdir"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> məzmunu göstərmək üçün hər iki displeydən istifadə edir"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 3fd8dcb..48b5c02 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećanje"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korišćenje Pristupačnosti"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikacije (<xliff:g id="NUMBER">%1$d</xliff:g>) koriste bateriju"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o bateriji i potrošnji podataka"</string>
@@ -314,7 +315,7 @@
     <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzika i zvuk"</string>
     <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"pristup muzici i audio sadržaju na uređaju"</string>
     <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i video snimci"</string>
-    <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i video snimcima na uređaju"</string>
+    <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i videima na uređaju"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
     <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Fizičke aktivnosti"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da pokrene usluge u prvom planu iz pozadine."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Preslikavanje na ekran nije moguće"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrebite drugi kabl i probajte ponovo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl ne podržava ekrane"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se ne povezuje pravilno sa ekranima"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 715aad3..12effa0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Праграмы, якія выкарыстоўваюць акумулятар"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Павелічэнне"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Выкарыстанне спецыяльных магчымасцей"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Дысплэй"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> выкарыстоўвае акумулятар"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Наступная колькасць праграм выкарыстоўваюць акумулятар: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Дакраніцеся, каб даведацца пра выкарыстанне трафіка і акумулятара"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Спадарожная праграма зможа запускаць актыўныя сэрвісы з фонавага рэжыму."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрафон даступны"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрафон заблакіраваны"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не ўдалося прадубліраваць змесціва на дысплэі"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Паспрабуйце скарыстаць іншы кабель"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Магчыма, кабель несумяшчальны з дысплэямі"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Магчыма, кабель USB-C не падыходзіць да дысплэяў"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Уключана функцыя Dual Screen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" выкарыстоўвае абодва экраны для паказу змесціва"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9dd971c..d18e4bc 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, използващи батерията"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Използване на услугите за достъпност"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва батерията"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> приложения използват батерията"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Докоснете за информация относно използването на батерията и преноса на данни"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Разрешава на дадено придружаващо приложение да стартира услуги на преден план, докато се изпълнява на заден план."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонът е налице"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонът е блокиран"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се копира огледално на дисплея"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Използвайте друг кабел и опитайте отново"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелът не поддържа дисплеи"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелът ви може да не се свързва правилно с дисплеи"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функцията Dual Screen е включена"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва и двата екрана, за да показва съдържание"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index a49e547..859f37d 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"কিছু অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বড় করে দেখা"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"অ্যাক্সেসিবিলিটি সংক্রান্ত ব্যবহার"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ডিসপ্লে"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপটি ব্যাটারি ব্যবহার করছে"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টি অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ব্যাটারি এবং ডেটার ব্যবহারের বিশদ বিবরণের জন্য ট্যাপ করুন"</string>
@@ -674,7 +675,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"ডিভাইস আনলক করুন"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"অন্য কোনওভাবে আনলক করার চেষ্টা করুন"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"আপনার \'ফিঙ্গারপ্রিন্ট\' শনাক্ত করা না গেলে \'ফেস আনলক\' ব্যবহার করুন, যেমন যখন আপনার আঙুল ভিজে থাকে"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"আপনার মুখ শনাক্ত করা না গেলে \'ফিঙ্গারপ্রিন্ট আনলক\' ব্যবহার করুন, যেমন যখন পর্যাপ্ত আলো নেই"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"পর্যাপ্ত আলো না থাকার পরিস্থিতিতে, আপনার মুখ শনাক্ত করা না গেলে \'ফিঙ্গারপ্রিন্ট আনলক\' ব্যবহার করুন"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"ফেস আনলক"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"\'ফেস আনলক\' ফিচার ব্যবহার করার ক্ষেত্রে হওয়া সমস্যা"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"আপনার ফেস মডেল মুছে দেওয়ার জন্য ট্যাপ করুন এবং তারপরে আবার ফেস যোগ করুন"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"কম্প্যানিয়ন অ্যাপকে, ব্যাকগ্রাউন্ড থেকে ফোরগ্রাউন্ড পরিষেবা চালু করার অনুমতি দেয়।"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্রোফোন উপলভ্য আছে"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্রোফোন ব্লক করা হয়েছে"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ডিসপ্লে মিরর করা যাচ্ছে না"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য কোনও কেবল ব্যবহার করে আবার চেষ্টা করুন"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কেবল, ডিসপ্লের সাথে কাজ নাও করতে পারে"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপনার USB-C কেবল, ডিসপ্লেতে সঠিকভাবে কানেক্ট নাও হতে পারে"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen চালু করা আছে"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"কন্টেন্ট দেখানোর জন্য <xliff:g id="APP_NAME">%1$s</xliff:g> দুটি ডিসপ্লে ব্যবহার করছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b0f1905..8e9fe9c 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećavanje"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korištenje pristupačnosti"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> troši bateriju"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje troše bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o potrošnji baterije i prijenosa podataka"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da iz pozadine pokrene usluge u prvom planu."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nije moguće preslikati na ekran"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabl i pokušajte ponovo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl možda neće podržavati ekrane"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se možda neće pravilno povezati s ekranima"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index be95847..dacdbf1 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacions que consumeixen bateria"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliació"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ús de les funcions d\'accessibilitat"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> està consumint bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacions estan consumint bateria"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca per obtenir informació sobre l\'ús de dades i de bateria"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"El micròfon està disponible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"És possible que el cable no sigui compatible amb pantalles"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"És possible que el teu cable USB-C no es connecti correctament a les pantalles"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La pantalla dual està activada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> està utilitzant les dues pantalles per mostrar contingut"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 32cff76..29e00fa 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikace spotřebovávají baterii"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zvětšení"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využití přístupnosti"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Displej"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> využívá baterii"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikace (<xliff:g id="NUMBER">%1$d</xliff:g>) využívají baterii"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o využití baterie a dat"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje doprovodné aplikaci spouštět z pozadí služby v popředí."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupný"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je zablokován"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nelze zrcadlit na displej"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použijte jiný kabel a zkuste to znovu"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možná nepodporuje displeje"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Váš kabel USB-C se možná nedokáže správně připojit k displejům"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkce Dual Screen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> používá k zobrazení obsahu oba displeje"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 75bf365..87703cd 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps, der bruger batteri"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørrelse"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Brug af hjælpefunktioner"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Skærm"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger batteri"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps bruger batteri"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryk for at se info om batteri- og dataforbrug"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillader, at en medfølgende app kan starte tjenester i forgrunden via tilladelser til tjenester i baggrunden."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgængelig"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokeret"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det er ikke muligt at spejle til skærmen"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Brug et andet kabel, og prøv igen"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablet understøtter muligvis ikke skærme"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dit USB-C-kabel kan muligvis ikke sluttes korrekt til skærmene"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er aktiveret"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger begge skærme til at vise indhold"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index e087fe2..3faf328 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Strom verbrauchende Apps"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergrößerung"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Nutzung der Bedienungshilfen"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> verbraucht Strom"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> Apps verbrauchen Strom"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Für Details zur Akku- und Datennutzung tippen"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ermöglicht einer Companion-App, Dienste im Vordergrund aus dem Hintergrund zu starten."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon ist verfügbar"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon ist blockiert"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kann nicht auf das Display gespiegelt werden"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Verwende ein anderes Kabel und versuch es noch einmal"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel unterstützt eventuell keine Bildschirme"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dein USB-C-Kabel ist möglicherweise nicht zum Verbinden von Bildschirmen geeignet"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ist aktiviert"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> nutzt zum Anzeigen von Inhalten beide Displays"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 9b713c99..af53ddf 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Εφαρμογές που καταναλώνουν μπαταρία"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Μεγιστοποίηση"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Χρήση προσβασιμότητας"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Οθόνη"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί μπαταρία"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> εφαρμογές χρησιμοποιούν μπαταρία"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Πατήστε για λεπτομέρειες σχετικά με τη χρήση μπαταρίας και δεδομένων"</string>
@@ -1376,7 +1377,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Εντοπίστηκε αναλογικό αξεσουάρ ήχου"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Η συνδεδεμένη συσκευή δεν είναι συμβατή με αυτό το τηλέφωνο. Πατήστε για να μάθετε περισσότερα."</string>
     <string name="adb_active_notification_title" msgid="408390247354560331">"Συνδέθηκε ο εντοπ. σφαλμ. USB"</string>
-    <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ./διόρθ. σφαλμ. USB"</string>
+    <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ. σφαλμ. USB"</string>
     <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
     <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Συνδέθηκε ο ασύρματος εντοπισμός σφαλμάτων"</string>
     <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Πατήστε, για να απενεργοποιήσετε τον ασύρματο εντοπισμό σφαλμάτων"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Επιτρέπει σε μια συνοδευτική εφαρμογή να εκκινεί υπηρεσίες στο προσκήνιο από το παρασκήνιο."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Το μικρόφωνο είναι διαθέσιμο"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Το μικρόφωνο έχει αποκλειστεί"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Δεν είναι δυνατός ο κατοπτρισμός στην οθόνη"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Χρησιμοποιήστε άλλο καλώδιο και δοκιμάστε ξανά"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Το καλώδιο μπορεί να μην υποστηρίζει οθόνες"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Το καλώδιο USB-C που έχετε ίσως να μην μπορεί να συνδεθεί σωστά σε οθόνες"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Διπλή οθόνη"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Η λειτουργία διπλής οθόνης είναι ενεργή"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Η εφαρμ. <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί και τις 2 οθόνες για εμφάνιση περιεχ."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index bc231f5..8917a82 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 29f4c78..5a0ed85 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen is on"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 5576054..bcf0790 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index ea95a513..7ebffc6 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index c09e6ce..b739768 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎Apps consuming battery‎‏‎‎‏‎"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‎Magnification‎‏‎‎‏‎"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎Accessibility usage‎‏‎‎‏‎"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎Display‎‏‎‎‏‎"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is using battery‎‏‎‎‏‎"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎ apps are using battery‎‏‎‎‏‎"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎Tap for details on battery and data usage‎‏‎‎‏‎"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎Allows a companion app to start foreground services from background.‎‏‎‎‏‎"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎Microphone is available‎‏‎‎‏‎"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎Microphone is blocked‎‏‎‎‏‎"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎Can\'t mirror to display‎‏‎‎‏‎"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎Use a different cable and try again‎‏‎‎‏‎"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎Cable may not support displays‎‏‎‎‏‎"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎Your USB-C cable may not connect to displays properly‎‏‎‎‏‎"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎Dual screen‎‏‎‎‏‎"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎Dual screen is on‎‏‎‎‏‎"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is using both displays to show content‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 723f833..ca9ff13 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que consumen batería"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está consumiendo batería"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps están consumiendo batería"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Presiona para obtener información sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede duplicar la pantalla"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un cable diferente y vuelve a intentarlo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Es posible que el cable no admita pantallas"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Es posible que el cable USB-C no se conecte a las pantallas de manera adecuada"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 79c6e26..97b1888 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicaciones que consumen batería"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando la batería"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicaciones están usando la batería"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para ver información detallada sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"El cable puede no ser compatible con pantallas"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Puede que tu cable USB‑C no sea adecuado para conectarse a pantallas"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 67c20f7..4a8666e 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Rakendused kasutavad akutoidet"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurendus"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Juurdepääsetavuse kasutus"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ekraan"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab akutoidet"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> rakendust kasutab akutoidet"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Aku ja andmekasutuse üksikasjade nägemiseks puudutage"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lubab kaasrakendusel taustal käivitada esiplaanil olevaid teenuseid."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon on saadaval"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon on blokeeritud"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ei saa ekraanile peegeldada"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Kasutage teist kaablit ja proovige uuesti"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kaabel ei pruugi ekraane toetada"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Teie USB-C-kaabel ei pruugi ekraanidega õigesti ühendust luua"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screeni režiim on sisse lülitatud"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab sisu kuvamiseks mõlemat ekraani"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index a897170..a163ed8 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Bateria kontsumitzen ari diren aplikazioak"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Lupa"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erabilerraztasun-hobespenak"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Pantaila"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ari da bateria erabiltzen"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikazio ari dira bateria erabiltzen"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Sakatu bateria eta datu-erabilerari buruzko xehetasunak ikusteko"</string>
@@ -476,7 +477,7 @@
     <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="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. Bateria-erabilera areagotzen du horrek."</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>
     <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Abian denean, aplikazioak gutxi gorabeherako kokapena lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan."</string>
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"atzitu kokapena atzeko planoan"</string>
@@ -2137,7 +2138,7 @@
     <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Aplikazioak ez du grabatzeko baimenik, baina baliteke audioa grabatzea USB bidezko gailu horren bidez."</string>
     <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Pantaila nagusia"</string>
     <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Atzera"</string>
-    <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Erabilitako azken aplikazioak"</string>
+    <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Azkenaldian erabilitako aplikazioak"</string>
     <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Jakinarazpenak"</string>
     <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Ezarpen bizkorrak"</string>
     <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Piztu edo itzaltzeko leihoa"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Aurreko planoko zerbitzuak atzeko planotik abiarazteko baimena ematen die aplikazio osagarriei."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Erabilgarri dago mikrofonoa"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Blokeatuta dago mikrofonoa"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ezin da islatu pantailan"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Erabili beste kable bat eta saiatu berriro"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Baliteke kablea pantailekin bateragarria ez izatea"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Baliteke USB-C kablea behar bezala ez konektatzea pantailetara"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen aktibatuta dago"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bi pantailak erabiltzen ari da edukia erakusteko"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index de1ba1a..c5a2ee6 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"برنامه‌های مصرف‌کننده باتری"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"درشت‌نمایی"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"کاربرد دسترس‌پذیری"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"نمایشگر"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده کردن از باتری است"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> برنامه درحال استفاده کردن از باتری هستند"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"برای جزئیات مربوط به مصرف باتری و داده، ضربه بزنید"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"به برنامه همراه اجازه می‌دهد سرویس‌های پیش‌نما را از پس‌زمینه راه‌اندازی کند."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"میکروفون دردسترس است"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"میکروفون مسدود شد"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"بازتاب دادن به نمایشگر ممکن نبود"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"شاید کابل از نمایشگر پشتیبانی نکند"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏کابل USB-C شما ممکن است به‌درستی به نمایشگرها وصل نشود"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"‫Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‏‫Dual Screen روشن است"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> از هر دو نمایشگر برای نمایش محتوا استفاده می‌کند"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 22d48e2..4a08b90 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkua kuluttavat sovellukset"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurennus"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Esteetön käyttö"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Näyttö"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää akkua."</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> sovellusta käyttää akkua."</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Katso lisätietoja akun ja datan käytöstä napauttamalla."</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Sallii kumppanisovelluksen aloittaa etualan palveluja taustalla."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni on käytettävissä"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni on estetty"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Näytön peilaaminen ei onnistu"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Käytä eri johtoa ja yritä uudelleen"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Johto ei ehkä tue näyttöjä"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-johtosi ei ehkä yhdisty näyttöihin kunnolla"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Kaksoisnäyttö"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kaksoisnäyttö on päällä"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää molempia näyttöjä sisällön näyttämiseen"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 928bf77..1b637db 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications qui sollicitent la pile"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Usage des fonctionnalités d\'accessibilité"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sollicite la pile"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications sollicitent la pile"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Touchez pour afficher des détails sur l\'utilisation de la pile et des données"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet à une application compagnon en arrière-plan de lancer des services d\'avant-plan."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Le microphone est accessible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Le microphone est bloqué"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossible de dupliquer l\'écran"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un câble différent et réessayez"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble peut ne pas être compatible avec les écrans"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C peut ne pas se connecter correctement aux écrans"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen activé"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher le contenu"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 2867516..4f8de0c 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications utilisant la batterie"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilisation de l\'accessibilité"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise la batterie"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications utilisent la batterie"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Appuyer pour obtenir des informations sur l\'utilisation de la batterie et des données"</string>
@@ -299,8 +300,8 @@
     <string name="android_system_label" msgid="5974767339591067210">"Système Android"</string>
     <string name="user_owner_label" msgid="8628726904184471211">"Passer au profil personnel"</string>
     <string name="managed_profile_label" msgid="7316778766973512382">"Passer au profil pro"</string>
-    <string name="user_owner_app_label" msgid="1553595155465750298">"Passer au <xliff:g id="APP_NAME">%1$s</xliff:g> personnel"</string>
-    <string name="managed_profile_app_label" msgid="367401088383965725">"Passer au <xliff:g id="APP_NAME">%1$s</xliff:g> professionnel"</string>
+    <string name="user_owner_app_label" msgid="1553595155465750298">"Passer à l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> personnelle"</string>
+    <string name="managed_profile_app_label" msgid="367401088383965725">"Passer à l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> professionnelle"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contacts"</string>
     <string name="permgroupdesc_contacts" msgid="9163927941244182567">"accéder à vos contacts"</string>
     <string name="permgrouplab_location" msgid="1858277002233964394">"Position"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Autorise une application associée à lancer des services de premier plan à partir de l\'arrière-plan."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Le micro est disponible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Le micro est bloqué"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Duplication impossible"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un autre câble et réessayez"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble n\'est peut-être pas compatible avec les écrans"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C n\'est peut-être pas connecté correctement à l\'écran"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Double écran"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Double écran activé"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher du contenu"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index e2073fb..b25cfcb 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacións que consomen batería"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidade"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo batería"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacións están consumindo batería"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para obter información sobre o uso de datos e a batería"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que unha aplicación complementaria, desde un segundo plano, inicie servizos en primeiro plano."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"O micrófono está dispoñible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O micrófono está bloqueado"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Non se pode proxectar contido na pantalla"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Cambia de cable e téntao de novo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Pode que o cable non sexa compatible con pantallas"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O teu cable USB-C pode que non se conecte ás pantallas de maneira adecuada"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen está activada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas as pantallas para mostrar contido"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 82b4a0d..fae5ebc 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ઍપ બૅટરીનો વપરાશ કરી રહ્યાં છે"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"મોટું કરવાની સુવિધા"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ઍક્સેસિબિલિટી વપરાશ"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ડિસ્પ્લે"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> બૅટરીનો ઉપયોગ કરી રહ્યું છે"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ઍપ બૅટરીનો ઉપયોગ કરી રહ્યાં છે"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"બૅટરી અને ડેટા વપરાશ વિશેની વિગતો માટે ટૅપ કરો"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"સાથી ઍપને બૅકગ્રાઉન્ડમાંથી ફૉરગ્રાઉન્ડ સેવાઓ શરૂ કરવાની મંજૂરી આપે છે."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"માઇક્રોફોન ઉપલબ્ધ છે"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ડિસ્પ્લે પર મિરર કરી શકાતું નથી"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"બીજા કોઈ કેબલનો ઉપયોગ કરો અને ફરી પ્રયાસ કરો"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"શક્ય છે કે કેબલ કદાચ ડિસ્પ્લેને સપોર્ટ ન આપતો હોય"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"તમારો USB-C કેબલ કદાચ ડિસ્પ્લે સાથે યોગ્ય રીતે કનેક્ટ ન થાય"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ચાલુ છે"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"કન્ટેન્ટ બતાવવા માટે <xliff:g id="APP_NAME">%1$s</xliff:g> બન્ને ડિસ્પ્લેનો ઉપયોગ કરી રહી છે"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 1f3a377..d4eb380 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"बैटरी की खपत करने वाले ऐप"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ज़ूम करने की सुविधा"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सुलभता सुविधाओं का इस्तेमाल"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"डिसप्ले"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बैटरी का इस्तेमाल कर रहा है"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ऐप बैटरी का इस्तेमाल कर रहे हैं"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बैटरी और डेटा खर्च की जानकारी के लिए छूएं"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"इससे साथी ऐप्लिकेशन को बैकग्राउंड में फ़ोरग्राउंड सेवाएं चलाने की अनुमति मिलती है."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफ़ोन इस्तेमाल किया जा सकता है"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफ़ोन को ब्लॉक किया गया है"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिसप्ले का कॉन्टेंट नहीं दिखाया जा सकता"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ऐसा हो सकता है कि केबल, डिसप्ले के साथ ठीक से काम न करे"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ऐसा हो सकता है कि यूएसबी-सी केबल, डिसप्ले के साथ ठीक से कनेक्ट न हो पाए"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen की सुविधा चालू है"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, कॉन्टेंट दिखाने के लिए दोनों स्क्रीन का इस्तेमाल कर रहा है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 389a956..81ac641 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije troše bateriju"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povećavanje"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Upotreba pristupačnosti"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje koriste bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite da biste vidjeli pojedinosti o potrošnji baterije i podatkovnom prometu"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Popratnoj aplikaciji omogućuje da iz pozadine pokrene usluge u prednjem planu."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Zrcaljenje na zaslon nije moguće"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabel i pokušajte ponovno"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možda ne podržava zaslone"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Vaš USB-C kabel možda nije ispravno povezan sa zaslonima"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dvostruki zaslon"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Uključen je dvostruki zaslon"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> upotrebljava oba zaslona za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 4bce83b..9a389db 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkumulátort használó alkalmazások"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Nagyítás"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Kisegítő lehetőségek használata"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Kijelző"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás használja az akkumulátort"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> alkalmazás használja az akkumulátort"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Koppintson az akkumulátor- és adathasználat részleteinek megtekintéséhez"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lehetővé teszi a társalkalmazások számára, hogy előtérben futó szolgáltatásokat indítsanak a háttérből."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"A mikrofon rendelkezésre áll"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"A mikrofon le van tiltva"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nem lehet tükrözni a kijelzőre"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Használjon másik kábelt, és próbálja újra"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Előfordulhat, hogy a kábel nem támogatja a kijelzőket"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Előfordulhat, hogy az USB-C kábellel nem csatlakoztathatók megfelelően a kijelzők"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A Dual Screen funkció be van kapcsolva"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> mindkét kijelzőt használja a tartalmak megjelenítésére"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index e74ec54..ccea0cc 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Մարտկոցի լիցքը ծախսող հավելվածներ"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Խոշորացում"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Հատուկ գործառույթների օգտագործում"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Էկրան"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"«<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածը ծախսում է մարտկոցի լիցքը"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> հավելված ծախսում է մարտկոցի լիցքը"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Հպեք՝ մարտկոցի և թրաֆիկի մանրամասները տեսնելու համար"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Թույլատրում է ուղեկցող հավելվածին ակտիվ ծառայություններ գործարկել ֆոնային ռեժիմից։"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Խոսափողը հասանելի է"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Խոսափողն արգելափակված է"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Չհաջողվեց հայելապատճենել էկրանին"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Օգտագործեք այլ մալուխ և նորից փորձեք"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Մալուխը կարող է համատեղելի չլինել էկրանների հետ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Հնարավոր է՝ USB-C մալուխը սխալ է միացված էկրանին"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen-ը միացված է"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն օգտագործում է երկու էկրանները"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index dc0993e..47bbefd 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikasi yang menggunakan baterai"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan aksesibilitas"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Layar"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan baterai"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikasi sedang meggunakan baterai"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketuk untuk melihat detail penggunaan baterai dan data"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Mengizinkan aplikasi pendamping memulai layanan latar depan dari latar belakang."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon diblokir"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat mencerminkan ke layar"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan coba lagi"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak mendukung layar"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C mungkin tidak terhubung dengan benar ke layar"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen aktif"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua layar untuk menampilkan konten"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 2af8969..f666308 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Forrit sem nota rafhlöðuorku"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Stækkun"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Aðgengisnotkun"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Skjár"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> notar rafhlöðuorku"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> forrit nota rafhlöðuorku"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ýttu til að fá upplýsingar um rafhlöðu- og gagnanotkun"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leyfir fylgiforriti að ræsa forgrunnsþjónustur úr bakgrunni."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Hljóðnemi er í boði"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Lokað er fyrir hljóðnemann"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekki er hægt að spegla á skjá"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Notaðu aðra snúru og reyndu aftur"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ekki er víst að snúran styðji skjái"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ekki er víst að USB-C-snúran tengist skjám á réttan hátt"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tveir skjáir"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kveikt er á tveimur skjám"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> er að nota báða skjái til að sýna efni"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 7ea502c..e6bc552 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"App che consumano la batteria"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ingrandimento"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilizzo dell\'accessibilità"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> sta consumando la batteria"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> app stanno consumando la batteria"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocca per conoscere i dettagli sull\'utilizzo dei dati e della batteria"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Consente a un\'app complementare di avviare servizi in primo piano dal background."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microfono disponibile"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfono bloccato"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossibile eseguire il mirroring al display"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un altro cavo e riprova"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Il cavo potrebbe non supportare i display"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Il cavo USB-C potrebbe non collegarsi correttamente ai display"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Doppio schermo"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Doppio schermo attivo"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> sta usando entrambi i display per mostrare contenuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 22cbab2..53ec382 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"אפליקציות שמרוקנות את הסוללה"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"הגדלה"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"שימוש בנגישות"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"מסך"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בסוללה"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> אפליקציות משתמשות בסוללה"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"אפשר להקיש כדי לקבל פרטים על צריכה של נתונים וסוללה"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ההרשאה הזו מאפשרת לאפליקציה נלווית להפעיל מהרקע שירותים שפועלים בחזית."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"המיקרופון זמין"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"המיקרופון חסום"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"לא ניתן לשקף למסך"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"צריך להשתמש בכבל שונה ולנסות שוב"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"יכול להיות שהכבל לא תומך במסכים"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏יכול להיות שכבל ה-USB-C לא יתחבר למסכים כמו שצריך"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"מצב שני מסכים"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"מצב שני מסכים מופעל"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בשני המסכים כדי להציג תוכן"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 06b3445..961a8da87 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"電池を消費しているアプリ"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"拡大"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ユーザー補助の使用"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ディスプレイ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」がバッテリーを使用しています"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個のアプリが電池を使用しています"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"タップしてバッテリーやデータの使用量を確認"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"バックグラウンドからのフォアグラウンド サービスの起動をコンパニオン アプリに許可します。"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"マイクを利用できます"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"マイクがブロックされています"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ディスプレイにミラーリングできません"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"別のケーブルでもう一度お試しください"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ケーブルはディスプレイに対応していない可能性があります"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C ケーブルがディスプレイに正しく接続されていない可能性があります"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"デュアル スクリーン"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"デュアル スクリーン: ON"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>は 2 画面でコンテンツを表示しています"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c8f6621f4..27f596b 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ბატარეის მხარჯავი აპები"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"გადიდება"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"მარტივი წვდომის გამოყენება"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ეკრანი"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ბატარეას"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"ბატარეას <xliff:g id="NUMBER">%1$d</xliff:g> აპი იყენებს"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"შეეხეთ ბატარეისა და მონაცემების მოხმარების შესახებ დეტალური ინფორმაციისთვის"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"საშუალებას აძლევს კომპანიონ აპს, რომ გაუშვას უპირატესი სერვისები ფონური რეჟიმიდან."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"მიკროფონი ხელმისაწვდომია"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"მიკროფონი დაბლოკილია"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ეკრანზე არეკვლა შეუძლებელია"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"გამოიყენეთ სხვა კაბელი და ცადეთ ხელახლა"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"კაბელს შეიძლება არ ჰქონდეს ეკრანების მხარდაჭერა"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"თქვენი USB-C კაბელი შეიძლება სათანადოდ არ უკავშირდებოდეს ეკრანებს"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ორმაგი ეკრანი"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ორმაგი ეკრანი ჩართულია"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ორივე ეკრანს შინაარსის საჩვენებლად"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1272c24..1faea61 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Батареяны пайдаланып жатқан қолданбалар"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ұлғайту"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Арнайы мүмкіндіктерді қолдану"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батареяны пайдалануда"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> қолданба батареяны пайдалануда"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарея мен деректер трафигі туралы білу үшін түртіңіз"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Қосымша қолданбаға экрандық режимдегі қызметтерді фоннан іске қосуға рұқсат беріледі."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон қолжетімді."</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон блокталған."</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дисплейге көшірмені көрсету мүмкін емес"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Басқа кабельмен әрекетті қайталап көріңіз."</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерді қолдамауы мүмкін"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелі дисплейлерге дұрыс жалғанбаған болуы мүмкін."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen функциясы қосулы"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы контентті көрсету үшін екі дисплейді де пайдаланады."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 807761e..470c39e 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"កម្មវិធីដែល​កំពុងប្រើថ្ម"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ការពង្រីក"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ការប្រើប្រាស់​ភាពងាយស្រួល"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ផ្ទាំងអេក្រង់"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើថ្ម"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"កម្មវិធីចំនួន <xliff:g id="NUMBER">%1$d</xliff:g> កំពុងប្រើថ្ម"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ចុចដើម្បីមើលព័ត៌មានលម្អិតអំពីការប្រើប្រាស់ទិន្នន័យ និងថ្ម"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"អនុញ្ញាតឱ្យកម្មវិធីដៃគូចាប់ផ្តើមសេវាកម្មផ្ទៃខាងមុខពីផ្ទៃខាងក្រោយ។"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"អាចប្រើ​មីក្រូហ្វូនបាន"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"មីក្រូហ្វូនត្រូវ​បានទប់ស្កាត់"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"មិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ប្រើខ្សែផ្សេង រួចព្យាយាមម្តងទៀត"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ខ្សែប្រហែលជាមិនអាចប្រើជាមួយផ្ទាំងអេក្រង់បានទេ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ខ្សែ USB-C របស់អ្នក​ប្រហែលជា​មិនអាចភ្ជាប់​ផ្ទាំងអេក្រង់​បានត្រឹមត្រូវទេ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"អេក្រង់ពីរ"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"អេក្រង់ពីរត្រូវបានបើក"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើផ្ទាំងអេក្រង់ទាំងពីរដើម្បីបង្ហាញខ្លឹមសារ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 742dfbb..e2f24f6 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಬ್ಯಾಟರಿಯನ್ನು ಉಪಯೋಗಿಸುತ್ತಿವೆ"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ಹಿಗ್ಗಿಸುವಿಕೆ"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ಪ್ರವೇಶಿಸುವಿಕೆಯ ಬಳಕೆ"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ಡಿಸ್‌ಪ್ಲೇ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್, ಬ್ಯಾಟರಿಯನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಬ್ಯಾಟರಿ ಬಳಸುತ್ತಿವೆ"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ಬ್ಯಾಟರಿ,ಡೇಟಾ ಬಳಕೆಯ ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಹಿನ್ನೆಲೆಯಿಂದ ಪ್ರಾರಂಭಿಸಲು ಕಂಪ್ಯಾನಿಯನ್ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿದೆ"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ಬೇರೆ ಕೇಬಲ್ ಬಳಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ಡಿಸ್‌ಪ್ಲೇಗಳನ್ನು ಕೇಬಲ್ ಬೆಂಬಲಿಸದಿರಬಹುದು"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ನಿಮ್ಮ USB-C ಕೇಬಲ್ ಡಿಸ್‌ಪ್ಲೇಗಳಿಗೆ ಸರಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆಗದಿರಬಹುದು"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ಆನ್ ಆಗಿದೆ"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ಕಂಟೆಂಟ್‌ ಅನ್ನು ತೋರಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಎರಡೂ ಡಿಸ್‌ಪ್ಲೇಗಳನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 5a98ef8..0608283 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"배터리를 소모하는 앱"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"확대"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"접근성 사용"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"디스플레이"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 배터리 사용 중"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"앱 <xliff:g id="NUMBER">%1$d</xliff:g>개에서 배터리 사용 중"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"탭하여 배터리 및 데이터 사용량 확인"</string>
@@ -674,7 +675,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"기기 잠금 해제"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"다른 잠금 해제 방법 사용"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"손가락에 물기가 있는 등 지문이 인식되지 않을 때는 얼굴 인식 잠금 해제를 사용하세요."</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"충분히 밝지 않은 경우 등 얼굴이 인식되지 않을 때는 지문 잠금 해제를 사용하세요."</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"주변이 어두운 경우 등 얼굴이 인식되지 않을 때는 지문 잠금 해제를 사용해 보세요."</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"얼굴 인식 잠금 해제"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"얼굴 인식 잠금 해제 문제"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"탭하여 얼굴 모델을 삭제한 후 다시 얼굴을 추가하세요"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"호환 앱이 백그라운드에서 포그라운드 서비스를 시작할 수 있게 허용합니다."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"마이크 사용 가능"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"마이크가 차단됨"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"디스플레이에 미러링할 수 없음"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"다른 케이블을 사용하여 다시 시도해 보세요."</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"디스플레이를 지원하지 않는 케이블일 수 있음"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"사용 중인 USB-C 케이블이 디스플레이에 제대로 연결되지 않을 수 있습니다."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen 켜짐"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 두 화면을 모두 사용하여 콘텐츠를 표시합니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 56c0696..853221e 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Колдонмолор батареяңызды коротууда"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Чоңойтуу"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Атайын мүмкүнчүлүктөрдүн колдонулушу"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу батареяны пайдаланып жатат"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> колдонмо батареяны пайдаланып жатат"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батареянын кубаты жана трафиктин көлөмү жөнүндө билүү үчүн таптап коюңуз"</string>
@@ -1384,7 +1385,7 @@
     <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Сыноо программасынын режими иштетилди"</string>
     <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Сыноо программасынын режимин өчүрүү үчүн баштапкы параметрлерге кайтарыңыз."</string>
     <string name="console_running_notification_title" msgid="6087888939261635904">"Сериялык консоль иштетилди"</string>
-    <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Аны өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
+    <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
     <string name="mte_override_notification_title" msgid="4731115381962792944">"Cынамык MTE иштетилди"</string>
     <string name="mte_override_notification_message" msgid="2441170442725738942">"Иштин майнаптуулугуна жана туруктуулугуна кедергиси тийиши мүмкүн. Өчүрүү үчүн түзмөктү өчүрүп-күйгүзүңүз. Эгер arm64.memtag.bootctl аркылуу иштетилген болсо, алдын ала \"none\" маанисин орнотуңуз."</string>
     <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"USB портунда суюктук же урандылар бар"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Көмөкчү колдонмого активдүү кызматтарды фондо иштетүүгө уруксат берет."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон жеткиликтүү"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Экранга күзгүдөй чагылдыруу мүмкүн эмес"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерди колдоого албашы мүмкүн"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабели дисплейлерге туура туташпашы мүмкүн"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen күйүк"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контентти эки түзмөктө тең көрсөтүүдө"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 8892f60..c743fc0 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ແອັບທີ່ກຳລັງໃຊ້ແບັດເຕີຣີ"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ການຂະຫຍາຍ"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ການໃຊ້ການຊ່ວຍເຂົ້າເຖິງ"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ຈໍສະແດງຜົນ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ແອັບກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ແຕະເພື່ອເບິ່ງລາຍລະອຽດການນຳໃຊ້ແບັດເຕີຣີ ແລະ ອິນເຕີເນັດ"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ອະນຸຍາດຈາກເບື້ອງຫຼັງໃຫ້ແອັບຊ່ວຍເຫຼືອເລີ່ມໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າ."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"ໄມໂຄຣໂຟນພ້ອມໃຫ້ນຳໃຊ້"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ໄມໂຄຣໂຟນຖືກບລັອກໄວ້"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ກະລຸນາໃຊ້ສາຍອື່ນແລ້ວລອງໃໝ່"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ສາຍອາດບໍ່ຮອງຮັບຈໍສະແດງຜົນ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ສາຍ USB-C ຂອງທ່ານອາດບໍ່ໄດ້ເຊື່ອມຕໍ່ກັບຈໍສະແດງຜົນຢ່າງຖືກຕ້ອງ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ໜ້າຈໍຄູ່"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ເປີດ Dual Screen ຢູ່"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ຈໍສະແດງຜົນທັງສອງເພື່ອສະແດງເນື້ອຫາ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 8b4ff97..ff665c5 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programos, naudojančios akumuliatoriaus energiją"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Didinimas"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pritaikomumo funkcijų naudojimas"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ekranas"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja akumuliatoriaus energiją"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programų, naudojančių akumuliatoriaus energiją: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Palieskite ir sužinokite išsamios informacijos apie akumuliatoriaus bei duomenų naudojimą"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leidžiama papildomai programai paleisti priekinio plano paslaugas fone."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonas pasiekiamas"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonas užblokuotas"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Negalima bendrinti ekrano vaizdo ekrane"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Naudokite kitą laiką ir bandykite dar kartą"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Laidas gali nepalaikyti ekranų"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Gali būti, kad USB-C laidu nepavyksta tinkamai prisijungti prie ekranų"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Įjungta „Dual Screen“"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja abu ekranus turiniui rodyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index c509378..4d369aa 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Lietotnes, kas patērē akumulatora jaudu"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Palielinājums"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pieejamības lietojums"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Displejs"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> izmanto akumulatoru"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> lietotne(-es) izmanto akumulatoru"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pieskarieties, lai skatītu detalizētu informāciju par akumulatora un datu lietojumu"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ļauj palīglietotnei sākt priekšplāna pakalpojumus no fona."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofons ir pieejams."</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofons ir bloķēts."</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nevar spoguļot displeju"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Izmantojiet citu vadu un mēģiniet vēlreiz."</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Iespējams, vads neatbalsta displejus"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Iespējams, jūsu USB-C vads nevarēs nodrošināt pareizu savienojumu ar displejiem."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen režīms"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ieslēgts Dual Screen režīms"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> satura rādīšanai izmanto abus displejus."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 109e967..29c9de8 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликации што ја трошат батеријата"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Зголемување"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Користење на пристапноста"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерија"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апликации користат батерија"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Допрете за детали за батеријата и потрошениот интернет"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволува придружна апликација да започне услуги во преден план од заднината."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонот е достапен"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонот е блокиран"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се отсликува за прикажување"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Користете друг кабел и обидете се повторно"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелот можеби не поддржува екрани"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Кабелот USB-C можеби нема да се поврзе правилно со екраните"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Вклучен е Dual Screen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ги користи двата екрани за да прикажува содржини"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 9ccf8b5..46587ee 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"മാഗ്നിഫിക്കേഷൻ"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ഉപയോഗസഹായി ഉപയോഗം"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ഡിസ്‌പ്ലേ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ബാറ്ററി, ഡാറ്റ ഉപയോഗം എന്നിവയുടെ വിശദാംശങ്ങളറിയാൻ ടാപ്പുചെയ്യുക"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"പശ്ചാത്തലത്തിൽ നിന്ന് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ ആരംഭിക്കാൻ സഹകാരി ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"മൈക്രോഫോൺ ലഭ്യമാണ്"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്‌തു"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"മറ്റൊരു കേബിൾ ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കുക"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"കേബിൾ, ഡിസ്പ്ലേകളെ പിന്തുണച്ചേക്കില്ല"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"നിങ്ങളുടെ USB-C കേബിൾ, ഡിസ്‌പ്ലേകളിലേക്ക് ശരിയായി കണക്റ്റ് ആയേക്കില്ല"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ഡ്യുവൽ സ്ക്രീൻ"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ഡ്യുവൽ സ്ക്രീൻ ഓണാണ്"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ഉള്ളടക്കം കാണിക്കാൻ <xliff:g id="APP_NAME">%1$s</xliff:g> രണ്ട് ഡിസ്പ്ലേകളും ഉപയോഗിക്കുന്നു"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index a9ef358..62a162e 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апп батарей ашиглаж байна"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Томруулах"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Хандалтын ашиглалт"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Дэлгэц"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батарей ашиглаж байна"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апп батарей ашиглаж байна"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарей, дата ашиглалтын талаар дэлгэрэнгүйг харахын тулд товшино уу"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дэмжигч аппад нүүрэн талын үйлчилгээнүүдийг ардаас эхлүүлэхийг зөвшөөрнө."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофоныг ашиглах боломжгүй байна"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофоныг блоклосон байна"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дэлгэцэд тусгал үүсгэх боломжгүй"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Өөр кабель ашиглаад, дахин оролдоно уу"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель нь дэлгэцүүдийг дэмждэггүй байж магадгүй"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Таны USB-C кабель дэлгэцүүдэд зохих ёсоор холбогдохгүй байж магадгүй"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen асаалттай байна"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контент харуулахын тулд хоёр дэлгэцийг хоёуланг нь ашиглаж байна"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 5f3b315..87227ee 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"बॅटरी लवकर संपवणारी अ‍ॅप्स"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"मॅग्निफिकेशन"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"अ‍ॅक्सेसिबिलिटी वापर"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बॅटरी वापरत आहे"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> अ‍ॅप्स बॅटरी वापरत आहेत"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्‍या तपशीलांसाठी टॅप करा"</string>
@@ -674,7 +675,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"डिव्हाइस अनलॉक"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"अनलॉक करण्याची दुसरी पद्धत वापरून पहा"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"तुमचे फिंगरप्रिंट ओळखले जात नाही, तेव्हा फेस अनलॉक वापरा, जसे की तुमची बोटे ओली असताना"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"तुमचा चेहरा ओळखला जात नाही, तेव्हा फिंगरप्रिंट अनलॉक वापरा, जसे की पुरेसा प्रकाश नसताना"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"पुरेसा प्रकाश नसणे इत्यादिमुळे तुमचा चेहरा ओळखला जात नाही, अशावेळी फिंगरप्रिंट अनलॉक वापरा"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"फेस अनलॉक"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"फेस अनलॉकसंबंधित समस्या"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"फेस मॉडेल हटवण्यासाठी टॅप करा, त्यानंतर तुमचा चेहरा पुन्हा जोडा"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"सहयोगी अ‍ॅपला बॅकग्राउंडमधून फोरग्राउंड सेवा सुरू करण्याची अनुमती देते."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"मायक्रोफोन उपलब्ध आहे"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"मायक्रोफोन ब्लॉक केलेला आहे"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेवर मिरर करू शकत नाही"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"वेगळी केबल वापरून पुन्हा प्रयत्न करा"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"केबल कदाचित डिस्प्लेना सपोर्ट करणार नाही"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तुमची USB-C केबल कदाचित डिस्प्लेना योग्यरीत्या कनेक्ट होणार नाही"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen सुरू आहे"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"आशय दाखवण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> दोन्ही डिस्प्ले वापरत आहे"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3d3fc7c..7794150 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apl yang menggunakan bateri"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan kebolehaksesan"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Paparan"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan bateri"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apl sedang menggunakan bateri"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketik untuk mendapatkan butiran tentang penggunaan kuasa bateri dan data"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Benarkan apl rakan memulakan perkhidmatan latar depan dari latar."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon disekat"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat menyegerakkan kepada paparan"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan cuba lagi"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak menyokong paparan"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C anda mungkin tidak bersambung kepada paparan dengan betul"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dwiskrin"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dwiskrin dihidupkan"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua-dua paparan untuk menunjukkan kandungan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 39dd043..3aaaf8b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"အက်ပ်များက ဘက်ထရီကုန်စေသည်"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ချဲ့ခြင်း"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"အများသုံးစွဲနိုင်မှုကို အသုံးပြုမှု"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ဖန်သားပြင်"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> က ဘက်ထရီကို အသုံးပြုနေသည်"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"အက်ပ် <xliff:g id="NUMBER">%1$d</xliff:g> ခုက ဘက်ထရီကို အသုံးပြုနေသည်"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ဘက်ထရီနှင့် ဒေတာအသုံးပြုမှု အသေးစိတ်ကို ကြည့်ရန် တို့ပါ"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"နောက်ခံမှနေ၍ မျက်နှာစာဝန်ဆောင်မှုများ စတင်ရန် တွဲဖက် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"မိုက်ခရိုဖုန်း သုံးနိုင်သည်"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ဖန်သားပြင်တွင် စကရင်ပွား၍ မရပါ"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"အခြားကေဘယ်ကြိုးသုံးပြီး ထပ်စမ်းကြည့်ပါ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ကေဘယ်ကြိုးက ဖန်သားပြင်များကို မပံ့ပိုးခြင်း ဖြစ်နိုင်သည်"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"သင့် USB-C ကေဘယ်ကြိုးသည် ဖန်သားပြင်များနှင့် မှန်ကန်စွာ ချိတ်ဆက်မထားခြင်း ဖြစ်နိုင်သည်"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ဖွင့်ထားသည်"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အကြောင်းအရာကို ပြရန် ဖန်သားပြင်နှစ်ခုစလုံးကို သုံးနေသည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 5b1f77c..9d6e805 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apper bruker batteri"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørring"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Bruk av Tilgjengelighet"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Skjerm"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker batteri"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apper bruker batteri"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trykk for detaljer om batteri- og databruk"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lar en følgeapp starte forgrunnstjenester fra bakgrunnen."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgjengelig"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokkert"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan ikke speile til skjermen"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Bruk en annen kabel og prøv igjen"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabelen støtter kanskje ikke skjermer"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-kabelen din kobler seg kanskje ikke til skjermer på riktig måte"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er på"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker begge skjermene til å vise innhold"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 603fad34..3b7de10 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"एपहरूले ब्याट्री खपत गर्दै छन्"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"जुम इन गर्ने सुविधा"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सर्वसुलभतासम्बन्धी सेवाहरूको प्रयोग"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले ब्याट्री प्रयोग गर्दै छ"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> एपहरूले ब्याट्री प्रयोग गर्दै छन्"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ब्याट्री र डेटाका प्रयोग सम्बन्धी विवरणहरूका लागि ट्याप गर्नुहोस्"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"यसले सहयोगी एपलाई ब्याकग्राउन्डमा फोरग्राउन्ड सेवाहरू चलाउने अनुमति दिन्छ।"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफोन अनम्युट गरिएको छ"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफोन म्युट गरिएको छ"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेमा मिरर गर्न सकिएन"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"अर्कै केबल प्रयोग गरी फेरि प्रयास गर्नुहोस्"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"यो केबल डिस्प्लेहरूमा प्रयोग गर्न नमिल्न सक्छ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तपाईंको USB-C केबल डिस्प्लेहरूमा राम्रोसँग नजोडिन सक्छ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen अन छ"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले सामग्री देखाउन दुई वटै डिस्प्ले प्रयोग गरिरहेको छ"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 50e261f..ae7d366 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps die de batterij gebruiken"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toegankelijkheidsgebruik"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Scherm"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt de batterij"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps gebruiken de batterij"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik voor batterij- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hiermee kan een bijbehorende app services op de voorgrond vanuit de achtergrond starten."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microfoon is beschikbaar"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfoon is geblokkeerd"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan niet spiegelen naar scherm"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik een andere kabel en probeer het opnieuw"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"De kabel ondersteunt misschien geen schermen"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Je USB-C-kabel sluit misschien niet goed aan op schermen"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen staat aan"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt beide schermen om content te tonen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 52c9bf2..af76df8 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ଆପ୍‍ଗୁଡ଼ିକ ବ୍ୟାଟେରୀ ଖର୍ଚ୍ଚ କରିଥା\'ନ୍ତି"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ମେଗ୍ନିଫିକେସନ"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ଆକ୍ସେସିବିଲିଟୀ ବ୍ୟବହାର"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ଡିସପ୍ଲେ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛି"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>ଟି ଆପ୍‍ ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛନ୍ତି"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ବ୍ୟାଟେରୀ ଏବଂ ଡାଟା ବ୍ୟବହାର ଉପରେ ବିବରଣୀ ପାଇଁ ଟାପ୍‍ କରନ୍ତୁ"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ପୃଷ୍ଠପଟରୁ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକ ଆରମ୍ଭ କରିବାକୁ ଏକ ସହଯୋଗୀ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ ଅଛି"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ଡିସପ୍ଲେ କରିବାକୁ ମିରର କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ଏକ ଭିନ୍ନ କେବୁଲ ବ୍ୟବହାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକୁ ସମର୍ଥନ କରିନପାରେ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ଆପଣଙ୍କ USB-C କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକ ସହ ସଠିକ ଭାବରେ କନେକ୍ଟ ହୋଇନପାରେ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ଚାଲୁ ଅଛି"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ବିଷୟବସ୍ତୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଉଭୟ ଡିସପ୍ଲେକୁ ବ୍ୟବହାର କରୁଛି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 8034be8..243e3e5 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ਬੈਟਰੀ ਦੀ ਖਪਤ ਕਰਨ ਵਾਲੀਆਂ ਐਪਾਂ"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ਵੱਡਦਰਸ਼ੀਕਰਨ"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ਪਹੁੰਚਯੋਗਤਾ ਵਰਤੋਂ"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ਡਿਸਪਲੇ"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ਐਪਾਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀਆਂ ਹਨ"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ਬੈਟਰੀ ਅਤੇ ਡਾਟਾ ਵਰਤੋਂ ਸਬੰਧੀ ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ਸੰਬੰਧੀ ਐਪ ਨੂੰ ਬੈਕਗ੍ਰਾਊਂਡ ਤੋਂ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਹੈ"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ਕੋਈ ਵੱਖਰੀ ਕੇਬਲ ਵਰਤ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀ USB-C ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਨਾਲ ਠੀਕ ਤਰ੍ਹਾਂ ਕਨੈਕਟ ਨਾ ਹੋਵੇ"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ਚਾਲੂ ਹੈ"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਸਮੱਗਰੀ ਨੂੰ ਦਿਖਾਉਣ ਲਈ ਦੋਵੇਂ ਡਿਸਪਲੇਆਂ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀ ਹੈ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index b66ec02..18bea3f 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacje zużywające baterię"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Powiększenie"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Użycie ułatwień dostępu"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Wyświetlacz"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> zużywa baterię"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Liczba aplikacji zużywających baterię: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i użycia danych"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Zezwala aplikacji towarzyszącej na uruchamianie usług działających na pierwszym planie, podczas gdy sama działa w tle."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon jest dostępny"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon jest zablokowany"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nie można utworzyć odbicia lustrzanego na wyświetlaczu"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Użyj innego kabla i spróbuj ponownie"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel może nie obsługiwać wyświetlaczy"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C może nie łączyć się prawidłowo z wyświetlaczami"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Podwójny ekran"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Włączono podwójny ekran"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> korzysta z obu wyświetlaczy, aby pokazać treści"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 9583c0e5..486318d 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -675,7 +676,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"Desbloqueio do dispositivo"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Tente desbloquear de outra maneira"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Use o Desbloqueio facial quando sua impressão digital não for reconhecida, como quando seus dedos estiverem molhados"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, como quando não houver luz suficiente"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, se estiver escuro, por exemplo"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problema com o Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index c83491e..784f2a9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão a consumir bateria"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilização da acessibilidade"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ecrã"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a consumir bateria."</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicações estão a consumir bateria."</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toque para obter detalhes acerca da utilização da bateria e dos dados"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que uma app associada em segundo plano inicie serviços em primeiro plano."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar para o ecrã"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use um cabo diferente e tente novamente"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"O cabo pode não suportar ecrãs"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O cabo USB-C pode não se ligar a ecrãs corretamente"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcionalidade Dual Screen ativada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a usar ambos os ecrãs para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 9583c0e5..486318d 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -675,7 +676,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"Desbloqueio do dispositivo"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Tente desbloquear de outra maneira"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Use o Desbloqueio facial quando sua impressão digital não for reconhecida, como quando seus dedos estiverem molhados"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, como quando não houver luz suficiente"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, se estiver escuro, por exemplo"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problema com o Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 3389c63..88f5044 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicațiile consumă bateria"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Mărire"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Folosirea accesibilității"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ecran"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicații folosesc bateria"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Atinge pentru mai multe detalii privind bateria și utilizarea datelor"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite unei aplicații partenere să inițieze servicii în prim-plan din fundal."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Microfonul este disponibil"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfonul este blocat"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nu se poate oglindi pe ecran"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Folosește alt cablu și încearcă din nou"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cablul poate să nu fie compatibil cu ecranele"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Cablul USB-C poate să nu se conecteze corespunzător la ecrane"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcția Dual screen este activată"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește ambele ecrane pentru a afișa conținut"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 920168a..7f5e87f 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, расходующие заряд"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Сервисы специальных возможностей"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" расходует заряд"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Несколько приложений (<xliff:g id="NUMBER">%1$d</xliff:g>) расходуют заряд"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Нажмите, чтобы проверить энергопотребление и трафик"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Сопутствующее приложение сможет запускать активные службы из фонового режима."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон доступен."</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон заблокирован."</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не удается дублировать на экран"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Используйте другой кабель или повторите попытку."</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель может не подходить для подключения к дисплеям"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Возможно, подключение дисплеев с помощью этого кабеля USB-C не поддерживается."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функция Dual Screen включена"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> использует оба экрана."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 5f743a4..e90f1a2 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"බැටරිය භාවිත කරන යෙදුම්"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"විශාලනය"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ප්‍රවේශ්‍යතා භාවිතය"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"සංදර්ශකය"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> බැටරිය භාවිත කරයි"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"යෙදුම් <xliff:g id="NUMBER">%1$d</xliff:g>ක් බැටරිය භාවිත කරයි"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"බැටරි හා දත්ත භාවිතය පිළිබඳව විස්තර සඳහා තට්ටු කරන්න"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"පසුබිමේ සිට පෙරබිම් සේවා ආරම්භ කිරීමට සහායක යෙදුමකට ඉඩ දෙයි."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"මයික්‍රෆෝනය තිබේ"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"මයික්‍රෆෝනය අවහිර කර ඇත"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"සංදර්ශකයට දර්පණය කළ නොහැක"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"වෙනස් කේබලයක් භාවිතා කර නැවත උත්සාහ කරන්න"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"කේබලය සංදර්ශක වෙත සහාය නොදැක්විය හැක"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ඔබේ USB-C කේබලයට සංදර්ශකවලට නිසි ලෙස සම්බන්ධ නොවිය හැක"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen සක්‍රීයයි"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"අන්තර්ගතය පෙන්වීමට <xliff:g id="APP_NAME">%1$s</xliff:g> සංදර්ශන දෙකම භාවිත කරයි"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6bbc110..3da576c 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikácie spotrebúvajúce batériu"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zväčšenie"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využitie dostupnosti"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Obrazovka"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> používa batériu"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikácie (<xliff:g id="NUMBER">%1$d</xliff:g>) používajú batériu"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o batérii a spotrebe dát"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje sprievodnej aplikácii spúšťať služby na popredí z pozadia."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofón je k dispozícii"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofón je blokovaný"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nedá sa zrkadliť do obrazovky"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použite iný kábel a skúste znova"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kábel nemusí podporovať obrazovky"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kábel USB‑C sa nemusí dať správne pripojiť k obrazovkám"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkcia Dual Screen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> zobrazuje obsah na oboch obrazovkách"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 3999f9f..5b731b7 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije, ki porabljajo energijo baterije"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povečava"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uporaba funkcij za dostopnost"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> porablja energijo baterije"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Toliko aplikacij porablja energijo baterije: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dotaknite se za prikaz podrobnosti porabe baterije in prenosa podatkov"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Spremljevalni aplikaciji dovoljuje, da storitve v ospredju zažene iz ozadja."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je na voljo"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ni mogoče zrcaliti zaslona"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Uporabite drug kabel in poskusite znova"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel morda ne podpira zaslonov"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C se morda ne more ustrezno povezati z zasloni."</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je vklopljen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> uporablja oba zaslona za prikaz vsebine."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index e746463..a4fd1bf1 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacionet që konsumojnë baterinë"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zmadhimi"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Përdorimi i qasshmërisë"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ekrani"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> po përdor baterinë"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikacione po përdorin baterinë"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trokit për detaje mbi baterinë dhe përdorimin e të dhënave"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lejon një aplikacion shoqërues të fillojë shërbimet në plan të parë nga sfondi."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni ofrohet"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni është i bllokuar"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nuk mund të pasqyrojë tek ekrani"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Përdor një kabllo tjetër dhe provo përsëri"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablloja nuk mund të mbështetë ekranet"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kablloja jote USB-C mund të mos lidhet siç duhet me ekranet"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen është aktiv"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> po i përdor të dyja ekranet për të shfaqur përmbajtje"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index b5aefaa..e5ae2f7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -291,6 +291,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликације које троше батерију"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увећање"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Коришћење Приступачности"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерију"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Апликације (<xliff:g id="NUMBER">%1$d</xliff:g>) користе батерију"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Додирните за детаље о батерији и потрошњи података"</string>
@@ -314,7 +315,7 @@
     <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Музика и звук"</string>
     <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"приступ музици и аудио садржају на уређају"</string>
     <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видео снимци"</string>
-    <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видео снимцима на уређају"</string>
+    <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видеима на уређају"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string>
     <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Физичке активности"</string>
@@ -2334,6 +2335,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозвољава пратећој апликацији да покрене услуге у првом плану из позадине."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон је доступан"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон је блокиран"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Пресликавање на екран није могуће"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Употребите други кабл и пробајте поново"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабл не подржава екране"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабл се не повезује правилно са екранима"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen је укључен"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи оба екрана за приказивање садржаја"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a24ca19..f3917fe 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Appar som drar batteri"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Förstoring"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Tillgänglighetsanvändning"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Skärm"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> drar batteri"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> appar drar batteri"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryck för information om batteri- och dataanvändning"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillåter att en tillhörande app startar förgrundstjänster i bakgrunden."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen är tillgänglig"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen är blockerad"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det går inte spegla till skärmen"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Använd en annan kabel och försök igen"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabeln kanske inte har stöd för skärmar"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Det kanske inte går att ansluta skärmar korrekt med den här USB-C-kabeln"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen är på"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> använder båda skärmarna för att visa innehåll"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7171486..95c1b25 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programu zinazotumia betri"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukuzaji"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Matumizi ya zana za ufikivu"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Skrini"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Huruhusu programu oanifu kuanzisha huduma zinazoonekana kwenye skrini kutoka katika huduma zinazoendelea chinichini."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Maikrofoni inapatikana"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Maikrofoni imezuiwa"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Imeshindwa kuakisi kwenye skrini"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Tumia kebo tofauti kisha ujaribu tena"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Huenda kebo haioani na skrini"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Huenda kebo yako ya USB-C isiunganishwe vizuri na skrini"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen imewasha"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia skrini zote kuonyesha maudhui"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 4fe363e..9ad4bd2 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"பேட்டரியைப் பயன்படுத்தும் ஆப்ஸ்"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"பெரிதாக்கல்"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"அணுகல்தன்மை உபயோகம்"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"டிஸ்ப்ளே"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகிறது"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகின்றன"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"பேட்டரி மற்றும் டேட்டா உபயோக விவரங்களைக் காண, தட்டவும்"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"பின்னணியிலிருந்து முன்புலச் சேவைகளைத் தொடங்க துணைத் தயாரிப்பு ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"மைக்ரோஃபோன் இயக்கப்பட்டது"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"டிஸ்ப்ளேயில் பிரதிபலிக்க முடியவில்லை"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"வெவ்வேறு கேபிள்களைப் பயன்படுத்தி மீண்டும் முயலவும்"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"டிஸ்ப்ளேக்களைக் கேபிள் ஆதரிக்காமல் இருக்கக்கூடும்"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"டிஸ்ப்ளேக்களில் உங்கள் USB-C கேபிள் சரியாக இணைக்கப்படாமல் இருக்கக்கூடும்"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"இரட்டைத் திரை"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"இரட்டைத் திரை அம்சம் இயக்கத்தில் உள்ளது"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"உள்ளடக்கத்தைக் காட்டுவதற்கு இரண்டு டிஸ்ப்ளேக்களையும் <xliff:g id="APP_NAME">%1$s</xliff:g> பயன்படுத்துகிறது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 8573c20c..d1c336d 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"బ్యాటరీని ఉపయోగిస్తున్న యాప్‌లు"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"మ్యాగ్నిఫికేషన్"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"యాక్సెసిబిలిటీ వినియోగం"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"డిస్‌ప్లే చేయండి"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> బ్యాటరీని ఉపయోగిస్తోంది"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> యాప్‌లు బ్యాటరీని ఉపయోగిస్తున్నాయి"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"బ్యాటరీ మరియు డేటా వినియోగ వివరాల కోసం నొక్కండి"</string>
@@ -673,8 +674,8 @@
     <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"వేలిముద్ర చిహ్నం"</string>
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"పరికర అన్‌లాక్"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"అన్‌లాక్ చేయడానికి మరొక మార్గాన్ని ట్రై చేయండి"</string>
-    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"మీ వేలిముద్ర గుర్తించబడనప్పుడు, మీ వేళ్లు తడిగా ఉన్నప్పుడు ఫేస్ అన్‌లాక్‌ను ఉపయోగించండి"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"మీ ఫేస్ గుర్తించబడనప్పుడు, తగినంత వెలుతురు లేనప్పుడు వేలిముద్ర అన్‌లాక్‌ను ఉపయోగించండి"</string>
+    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"మీ వేళ్లు తడిగా ఉండటం లేక ఇతరత్రా కారణాల వల్ల మీ వేలిముద్రను గుర్తించకపోతే, ఫేస్ అన్‌లాక్‌ను ఉపయోగించండి"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"తగినంత వెలుతురు లేకపోవడం లేక ఇతరత్రా కారణాల వల్ల మీ ఫేస్ గుర్తించబడనప్పుడు, వేలిముద్ర అన్‌లాక్‌ను ఉపయోగించండి"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"ఫేస్ అన్‌లాక్"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"ఫేస్ అన్‌లాక్‌తో సమస్య"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ఫేస్ మోడల్‌ను తొలగించడానికి నొక్కండి, ఆపై మీ ముఖాన్ని మళ్లీ జోడించండి"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"బ్యాక్‌గ్రౌండ్ నుండి ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ప్రారంభించడానికి సహాయక యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"మైక్రోఫోన్ అందుబాటులో ఉంది"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"మైక్రోఫోన్ బ్లాక్ చేయబడింది"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"డిస్‌ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"వేరే కేబుల్‌ను ఉపయోగించి, మళ్లీ ట్రై చేయండి"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"డిస్‌ప్లేలను కేబుల్ సపోర్ట్ చేయకపోవచ్చు"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"మీ USB-C కేబుల్, డిస్‌ప్లేలకు సరిగ్గా కనెక్ట్ కాకపోవచ్చు"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ఆన్‌లో ఉంది"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"కంటెంట్‌ను చూపడం కోసం <xliff:g id="APP_NAME">%1$s</xliff:g> రెండు డిస్‌ప్లేలనూ ఉపయోగిస్తోంది"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index eb4e2f7..4f53fcd 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"แอปหลายแอปกำลังใช้แบตเตอรี่"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"การขยาย"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"การใช้งานการช่วยเหลือพิเศษ"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"จอแสดงผล"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้แบตเตอรี่"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"แอป <xliff:g id="NUMBER">%1$d</xliff:g> แอปกำลังใช้แบตเตอรี่"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"แตะเพื่อดูรายละเอียดเกี่ยวกับแบตเตอรี่และปริมาณการใช้อินเทอร์เน็ต"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"อนุญาตจากเบื้องหลังให้แอปที่ใช้ร่วมกันเริ่มการทำงานของบริการที่ทำงานอยู่เบื้องหน้า"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"ไมโครโฟนพร้อมใช้งาน"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"ไมโครโฟนถูกบล็อก"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"มิเรอร์ไปยังจอแสดงผลไม่ได้"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"โปรดใช้สายอื่นและลองอีกครั้ง"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"สายสัญญาณอาจไม่รองรับจอแสดงผล"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"สาย USB-C อาจเชื่อมต่อกับจอแสดงผลอย่างไม่ถูกต้อง"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen เปิดอยู่"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้จอแสดงผลทั้งสองจอเพื่อแสดงเนื้อหา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7249c51..2477698 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Mga app na kumokonsumo ng baterya"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pag-magnify"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Paggamit sa pagiging accessible"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Gumagamit ng baterya ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Gumagamit ng baterya ang <xliff:g id="NUMBER">%1$d</xliff:g> (na) app"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"I-tap para sa mga detalye tungkol sa paggamit ng baterya at data"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Nagbibigay-daan sa kasamang app na magsimula ng mga serbisyo sa foreground mula sa background."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Available ang mikropono"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Naka-block ang mikropono"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Hindi makapag-mirror sa display"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gumamit ng ibang cable at subukan ulit"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Posibleng hindi sinusuportahan ng cable ang mga display"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Posibleng hindi kumonekta nang maayos sa mga display ang iyong USB-C cable"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Naka-on ang dual screen"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang parehong display para magpakita ng content"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8f02efd..2ebfe91 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Pil kullanan uygulamalar"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Büyütme"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erişilebilirlik kullanımı"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> pil kullanıyor"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> uygulama pil kullanıyor"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pil ve veri kullanımı ile ilgili ayrıntılar için dokunun"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tamamlayıcı uygulamanın arka plandan ön plan hizmetlerini başlatmasına izin verir."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon kullanılabilir"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon engellenmiş"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekrana yansıtılamıyor"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Farklı kablo kullanarak tekrar deneyin"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablo, ekranları desteklemeyebilir"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kablonuz ekranlara doğru şekilde bağlanamayabilir"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen açık"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, içeriği göstermek için her iki ekranı da kullanıyor"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 936c978b..d4cd207 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -292,6 +292,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Додатки, що використовують заряд акумулятора"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Збільшення"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Використання спеціальних можливостей"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує заряд акумулятора"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Додатків, що використовують заряд акумулятора: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Торкніться, щоб перевірити використання акумулятора й трафік"</string>
@@ -675,8 +676,8 @@
     <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Значок відбитка пальця"</string>
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"Розблокування пристрою"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Спробуйте інший спосіб розблокування"</string>
-    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Розблоковуйте пристрій за допомогою фейс-контролю, коли не вдається розпізнати ваш відбиток пальця (наприклад, коли у вас мокрі пальці)"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Розблоковуйте пристрій відбитком пальця, коли не вдається розпізнати ваше обличчя (наприклад, коли недостатньо світла)"</string>
+    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Якщо пристрій не розпізнає ваш відбиток пальця (наприклад, коли у вас мокрі руки), використовуйте фейс-контроль"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Якщо пристрій не розпізнає ваше обличчя (наприклад, коли освітлення погане), використовуйте відбиток пальця"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Фейс-контроль"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Сталася помилка з фейсконтролем"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Натисніть, щоб видалити свою модель обличчя, а потім знову додайте її"</string>
@@ -2335,6 +2336,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволяє супутньому додатку запускати активні сервіси у фоновому режимі."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрофон доступний"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрофон заблоковано"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Неможливо дублювати на дисплей"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Скористайтесь іншим кабелем і повторіть спробу"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель може не підтримувати дисплеї"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ваш кабель USB-C може не підключатися до дисплеїв належним чином"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen увімкнено"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує обидва екрани для показу контенту"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 631a573..8634294 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"ایپس بیٹری خرچ کر رہی ہیں"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"میگنیفکیشن"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ایکسیسبیلٹی کا استعمال"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"ڈسپلے"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> بیٹری کا استعمال کر رہی ہے"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ایپس بیٹری کا استعمال کر رہی ہیں"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"بیٹری اور ڈیٹا استعمال کے بارے میں تفصیلات کے لیے تھپتھپائیں"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ساتھی ایپ کو پس منظر سے پیش منظر کی سروسز شروع کرنے کی اجازت دیتی ہے۔"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"مائیکروفون دستیاب ہے"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"مائیکروفون مسدود ہے"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ڈسپلے پر دو طرفہ مطابقت پذیری ممکن نہیں ہے"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"مختلف کیبل استعمال کریں اور دوبارہ کوشش کریں"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ہو سکتا ہے کہ کیبل ڈسپلیز کو سپورٹ نہ کرے"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏ہو سکتا ہے کہ آپ کی USB-C کیبل مناسب طریقے سے ڈسپلیز سے منسلک نہ ہو"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"دوہری اسکرین"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"دوہری اسکرین آن ہے"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"مواد دکھانے کیلئے <xliff:g id="APP_NAME">%1$s</xliff:g> دونوں ڈسپلیز استعمال کر رہی ہے"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 4262342..fdea194 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareya quvvatini sarflayotgan ilovalar"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Kattalashtirish"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Qulayliklar ishlatilishi"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi batareya quvvatini sarflamoqda"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ta ilova batareya quvvatini sarflamoqda"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya va trafik sarfi tafsilotlari uchun ustiga bosing"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hamroh ilovaga faol xizmatlarni fonda ishga tushirishga ruxsat beradi."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon yoqildi"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon bloklandi"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeyga translatsiya qilinmaydi"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Boshqa kabel yordamida qayta urining"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeylar bilan ishlamasligi mumkin"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabelingiz displeylarga toʻgʻri ulanmasligi mumkin"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Ikkita ekran"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ikki ekranli rejim yoniq"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kontentni ikkala ekranda chiqarmoqda"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 78318b1..12a61ed 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Các ứng dụng tiêu thụ pin"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Phóng to"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Việc sử dụng tính năng hỗ trợ tiếp cận"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Màn hình"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang sử dụng pin"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng đang sử dụng pin"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Nhấn để biết chi tiết về mức sử dụng dữ liệu và pin"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Cho phép một ứng dụng đồng hành bắt đầu các dịch vụ trên nền trước từ nền."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Micrô đang hoạt động"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Micrô đang bị chặn"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Không chiếu được nội dung lên màn hình"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Hãy dùng một cáp khác rồi thử lại"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Có thể cáp không hỗ trợ màn hình"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Có thể cáp USB-C của bạn chưa kết nối đúng cách với màn hình"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Chế độ Dual screen bật"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang dùng cả hai màn hình để thể hiện nội dung"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 16c1013..d79a772 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"消耗电量的应用"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大功能"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"无障碍功能使用情况"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"显示屏"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在消耗电量"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 个应用正在消耗电量"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"点按即可详细了解电量和流量消耗情况"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允许配套应用从后台启动前台服务。"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"麦克风可用"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"麦克风已被屏蔽"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"无法镜像到显示屏"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"请改用其他数据线并重试"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"数据线可能不支持显示屏"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"您的 USB-C 数据线可能没有正确连接到显示屏"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"双屏幕"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"双屏幕功能已开启"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在使用双屏幕显示内容"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index ccdd945..fe3644e 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"耗用電量的應用程式"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情況"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"顯示屏"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用電量"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在使用電量"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕按即可查看電池和數據用量詳情"</string>
@@ -1891,7 +1892,7 @@
     <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{1 小時}other{# 小時}}"</string>
     <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{1 小時}other{# 小時}}"</string>
     <string name="zen_mode_until_next_day" msgid="1403042784161725038">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
-    <string name="zen_mode_until" msgid="2250286190237669079">"完成時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_until" msgid="2250286190237669079">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_alarm" msgid="7046911727540499275">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (下一次響鬧)"</string>
     <string name="zen_mode_forever" msgid="740585666364912448">"直至你關閉為止"</string>
     <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"直至你關閉「請勿騷擾」功能"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"可以使用麥克風"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"已封鎖麥克風"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他連接線,然後再試一次"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"連接線可能不支援顯示屏"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"你的 USB-C 連接線可能未妥善連接顯示屏"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"已開啟雙螢幕功能"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 81bdc37..5b65201 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"正在耗用電量的應用程式"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情形"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"螢幕"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在耗用電量"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在耗用電量"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕觸即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"麥克風已可使用"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"麥克風已封鎖"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他傳輸線,然後再試一次"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"傳輸線可能不支援螢幕"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C 傳輸線可能未妥善連接到螢幕"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"雙螢幕功能已啟用"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a2eb545..b701abe 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -290,6 +290,7 @@
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Izinhlelo zokusebenza ezidla ibhethri"</string>
     <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukukhuliswa"</string>
     <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ukusetshenziswa kokufinyeleleka"</string>
+    <string name="notification_channel_display" msgid="6905032605735615090">"Bonisa"</string>
     <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa ibhethri"</string>
     <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> izinhlelo zokusebenza zisebenzisa ibhethri"</string>
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Thepha ngemininingwane ekusetshenzisweni kwebhethri nedatha"</string>
@@ -2333,6 +2334,10 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ivumela i-app ehambisanayo ukuthi iqale amasevisi angaphambili kusukela ngemuva."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"Imakrofoni iyatholakala"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"Imakrofoni ivinjiwe"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ayikwazi ukufanisa nesibonisi"</string>
+    <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Sebenzisa ikhebuli ehlukile bese uyazama futhi"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ikhebuli ingase ingasekeli iziboniso"</string>
+    <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ikhebuli yakho ye-USB-C ingase ingaxhumi kahle kuzibonisi"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Isikrini esikabili"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Isikrini esikabili sivuliwe"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa zombili izibonisi ukukhombisa okuqukethwe"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e54347f..3496994 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10090,6 +10090,15 @@
     <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
     <attr name="lStar" format="float"/>
 
+    <!-- The attributes of the {@code <locale-config>} tag. -->
+    <!-- @FlaggedApi("android.content.res.default_locale") -->
+    <declare-styleable name="LocaleConfig">
+        <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+       the strings in values/strings.xml (the default strings in the directory with no locale
+       qualifier) are in. -->
+        <attr name="defaultLocale" format="string"/>
+    </declare-styleable>
+
     <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
     <declare-styleable name="LocaleConfig_Locale">
         <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
@@ -10118,6 +10127,9 @@
              screen that can be used to provide more information about a provider. For
              longer strings it will be truncated. -->
         <attr name="settingsSubtitle" format="string" />
+        <!-- Fully qualified class name of an activity that allows the user to modify
+             the settings for this service. -->
+        <attr name="settingsActivity" />
     </declare-styleable>
 
     <!-- A list of capabilities that indicates to the OS what kinds of credentials
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7ef81ab..ab71b41 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1026,6 +1026,12 @@
     <!-- Duration, in milliseconds, of the display white balance animated transitions. -->
     <integer name="config_displayWhiteBalanceTransitionTime">3000</integer>
 
+    <!-- Duration, in milliseconds, of the display white balance animated transitions when increasing cct. -->
+    <integer name="config_displayWhiteBalanceTransitionTimeIncrease">1000</integer>
+
+    <!-- Duration, in milliseconds, of the display white balance animated transitions when decreasing cct. -->
+    <integer name="config_displayWhiteBalanceTransitionTimeDecrease">40000</integer>
+
     <!-- Device states where the sensor based rotation values should be reversed around the Z axis
          for the default display.
          TODO(b/265312193): Remove this workaround when this bug is fixed.-->
@@ -1235,6 +1241,21 @@
    <!-- Activity name for the default target activity to be launched. [DO NOT TRANSLATE] -->
    <string name="config_primaryShortPressTargetActivity" translatable="false"></string>
 
+    <!-- Whether a single short press on POWER should be launched without multi-press delay.
+        When this value is set to true, POWER button's single short press behavior will execute
+        immediately upon key-up regardless of whether this press will be part of a multi-press
+        gesture in the future(therefore, not waiting for a multi-press detecting delay).
+
+       For Example, let's say a power button single press launches the app menu and power button
+       double press launches the camera. By configuring this variable to true, user will observe 2
+       things in order upon a double press:
+          1. App menu pops up immediately upon the first key up.
+          2. Camera starts as the double press behavior.
+
+        Note that this config has no effect on long press behavior.
+    -->
+    <bool name="config_shortPressEarlyOnPower">false</bool>
+
     <!-- Control the behavior of the search key.
             0 - Launch default search activity
             1 - Launch target activity defined by config_searchKeyTargetActivity
@@ -3982,6 +4003,13 @@
          limit is unknown. -->
     <item name="config_hapticChannelMaxVibrationAmplitude" format="float" type="dimen">0</item>
 
+    <!-- The fixed keyboard vibration strength in [0,1], or -1 to indicate the strength not fixed
+         and should depend on the touch feedback intensity user setting -->
+    <item name="config_keyboardHapticFeedbackFixedAmplitude" format="float" type="dimen">-1</item>
+
+    <!-- The default value for keyboard vibration toggle in settings. -->
+    <bool name="config_defaultKeyboardVibrationEnabled">true</bool>
+
     <!-- If the device should still vibrate even in low power mode, for certain priority vibrations
      (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
     <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
@@ -5535,6 +5563,14 @@
         <!-- Add packages here -->
     </string-array>
 
+    <!-- Enable pause wallpaper rendering upon state change such as app launch -->
+    <bool name="config_pauseWallpaperRenderWhenStateChangeEnabled">false</bool>
+
+    <!-- The list of packages to pause wallpaper rendering upon state change such as app launch -->
+    <string-array name="pause_wallpaper_render_when_state_change" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- Whether or not to hide the navigation bar when the soft keyboard is visible in order to
          create additional screen real estate outside beyond the keyboard. Note that the user needs
          to have a confirmed way to dismiss the keyboard when desired. -->
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8fb48bc..e42962c 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -31,4 +31,13 @@
     is a relatively expensive operation, this throttle period may need to be adjusted for low-power
     devices-->
     <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+
+    <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
+    stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
+    <integer name="config_powerStatsAggregationPeriod">14400000</integer>
+
+    <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery
+    history time for every aggregated power stats span that is stored stored in PowerStatsStore.
+    It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) -->
+    <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0..4cd4f63 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -110,6 +110,8 @@
   <eat-comment/>
 
   <staging-public-group type="attr" first-id="0x01bd0000">
+    <!-- @FlaggedApi("android.content.res.default_locale") -->
+    <public name="defaultLocale"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fac6aac..a2a4e34f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -826,6 +826,9 @@
          security policy. [CHAR_LIMIT=NONE]-->
     <string name="notification_channel_accessibility_security_policy">Accessibility usage</string>
 
+    <!-- Text shown when viewing channel settings for notifications related to displays -->
+    <string name="notification_channel_display">Display</string>
+
     <!-- Label for foreground service notification when one app is running.
     [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
     <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
@@ -6290,6 +6293,16 @@
     <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
     <string name="mic_access_off_toast">Microphone is blocked</string>
 
+    <!-- Title of connected display unavailable notifications. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_unavailable_notification_title">Can\'t mirror to display</string>
+    <!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_unavailable_notification_content">Use a different cable and try again</string>
+
+    <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
+    <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string>
+
     <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
     <string name="concurrent_display_notification_name">Dual screen</string>
     <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 643f4b1..14bbb96 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -471,6 +471,7 @@
   <java-symbol type="string" name="config_primaryShortPressTargetActivity" />
   <java-symbol type="integer" name="config_doublePressOnStemPrimaryBehavior" />
   <java-symbol type="integer" name="config_triplePressOnStemPrimaryBehavior" />
+  <java-symbol type="bool" name="config_shortPressEarlyOnPower" />
   <java-symbol type="string" name="config_doublePressOnPowerTargetActivity" />
   <java-symbol type="integer" name="config_searchKeyBehavior" />
   <java-symbol type="string" name="config_searchKeyTargetActivity" />
@@ -1997,6 +1998,7 @@
   <java-symbol type="drawable" name="stat_sys_throttled" />
   <java-symbol type="drawable" name="vpn_connected" />
   <java-symbol type="drawable" name="vpn_disconnected" />
+  <java-symbol type="drawable" name="usb_cable_unknown_issue" />
   <java-symbol type="id" name="ask_checkbox" />
   <java-symbol type="id" name="compat_checkbox" />
   <java-symbol type="id" name="original_app_icon" />
@@ -2052,6 +2054,8 @@
   <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
+  <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" />
+  <java-symbol type="bool" name="config_defaultKeyboardVibrationEnabled" />
   <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
   <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
   <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
@@ -3486,6 +3490,8 @@
   <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" />
   <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
   <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeIncrease" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeDecrease" />
 
   <!-- Device states where the sensor based rotation values should be reversed around the Z axis
        for the default display.
@@ -3811,6 +3817,7 @@
   <java-symbol type="string" name="notification_channel_do_not_disturb" />
   <java-symbol type="string" name="notification_channel_accessibility_magnification" />
   <java-symbol type="string" name="notification_channel_accessibility_security_policy" />
+  <java-symbol type="string" name="notification_channel_display" />
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultFieldClassificationService" />
   <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
@@ -4302,6 +4309,8 @@
   <java-symbol type="string" name="config_factoryResetPackage" />
   <java-symbol type="array" name="config_highRefreshRateBlacklist" />
   <java-symbol type="array" name="config_forceSlowJpegModeList" />
+  <java-symbol type="array" name="pause_wallpaper_render_when_state_change" />
+  <java-symbol type="bool" name="config_pauseWallpaperRenderWhenStateChangeEnabled" />
 
   <java-symbol type="array" name="config_smallAreaDetectionAllowlist" />
 
@@ -5064,6 +5073,10 @@
   <java-symbol type="array" name="device_state_notification_thermal_contents"/>
   <java-symbol type="array" name="device_state_notification_power_save_titles"/>
   <java-symbol type="array" name="device_state_notification_power_save_contents"/>
+  <java-symbol type="string" name="connected_display_unavailable_notification_title"/>
+  <java-symbol type="string" name="connected_display_unavailable_notification_content"/>
+  <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/>
+  <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/>
   <java-symbol type="string" name="concurrent_display_notification_name"/>
   <java-symbol type="string" name="concurrent_display_notification_active_title"/>
   <java-symbol type="string" name="concurrent_display_notification_active_content"/>
@@ -5116,6 +5129,8 @@
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
   <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
+  <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
+  <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
 
   <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index af8c69e..3a2e50a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -54,6 +54,9 @@
     <!-- Azerbaijan: 4-5 digits, known premium codes listed -->
     <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" />
 
+    <!-- Bangladesh: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="bd" pattern="\\d{1,5}" free="16672" />
+
     <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
     <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
 
@@ -145,7 +148,7 @@
     <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
 
     <!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" />
+    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />
 
     <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
@@ -190,7 +193,7 @@
     <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" />
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
 
     <!-- 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" />
@@ -205,7 +208,7 @@
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />
 
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
-    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
+    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
 
     <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 054d10c..6be553b 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -40,6 +40,8 @@
         "androidx.test.rules",
         "truth",
         "testng",
+        "android.hardware.radio.flags-aconfig-java",
+        "flag-junit",
         "mockito-target-extended",
     ],
     libs: ["android.test.base"],
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index d638fed..d4a88c4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -16,8 +16,6 @@
 
 package android.hardware.radio;
 
-import static com.google.common.truth.Truth.assertWithMessage;
-
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -36,8 +34,14 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
+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 com.google.common.truth.Expect;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -143,12 +147,17 @@
     @Mock
     private RadioTuner.Callback mTunerCallbackMock;
 
+    @Rule
+    public final Expect mExpect = Expect.create();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void getIdentifierTypes_forFilter() {
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes())
+        mExpect.withMessage("Filtered identifier types").that(filter.getIdentifierTypes())
                 .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES);
     }
 
@@ -157,7 +166,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filtered identifiers").that(filter.getIdentifiers())
+        mExpect.withMessage("Filtered identifiers").that(filter.getIdentifiers())
                 .containsExactlyElementsIn(FILTER_IDENTIFIERS);
     }
 
@@ -166,7 +175,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filter including categories")
+        mExpect.withMessage("Filter including categories")
                 .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES);
     }
 
@@ -175,7 +184,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filter excluding modifications")
+        mExpect.withMessage("Filter excluding modifications")
                 .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS);
     }
 
@@ -184,7 +193,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filter vendor obtained from filter without vendor filter")
+        mExpect.withMessage("Filter vendor obtained from filter without vendor filter")
                 .that(filter.getVendorFilter()).isNull();
     }
 
@@ -192,13 +201,13 @@
     public void getVendorFilter_forFilterWithVendorFilter() {
         ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER);
 
-        assertWithMessage("Filter vendor obtained from filter with vendor filter")
+        mExpect.withMessage("Filter vendor obtained from filter with vendor filter")
                 .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER);
     }
 
     @Test
     public void describeContents_forFilter() {
-        assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
+        mExpect.withMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
     }
 
     @Test
@@ -206,7 +215,7 @@
         ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Hash code of the same filter")
+        mExpect.withMessage("Hash code of the same filter")
                 .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode());
     }
 
@@ -214,7 +223,7 @@
     public void hashCode_withDifferentFilters_notEquals() {
         ProgramList.Filter filterCompared = new ProgramList.Filter();
 
-        assertWithMessage("Hash code of the different filter")
+        mExpect.withMessage("Hash code of the different filter")
                 .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode());
     }
 
@@ -227,7 +236,7 @@
 
         ProgramList.Filter filterFromParcel =
                 ProgramList.Filter.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Filter created from parcel")
+        mExpect.withMessage("Filter created from parcel")
                 .that(filterFromParcel).isEqualTo(TEST_FILTER);
     }
 
@@ -235,36 +244,37 @@
     public void newArray_forFilterCreator() {
         ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
     }
 
     @Test
     public void isPurge_forChunk() {
-        assertWithMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE);
+        mExpect.withMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE);
     }
 
     @Test
     public void isComplete_forChunk() {
-        assertWithMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete())
+        mExpect.withMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete())
                 .isEqualTo(IS_COMPLETE);
     }
 
     @Test
     public void getModified_forChunk() {
-        assertWithMessage("Modified program info in chunk")
+        mExpect.withMessage("Modified program info in chunk")
                 .that(FM_DAB_ADD_CHUNK.getModified())
                 .containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2);
     }
 
     @Test
     public void getRemoved_forChunk() {
-        assertWithMessage("Removed program identifiers in chunk")
+        mExpect.withMessage("Removed program identifiers in chunk")
                 .that(FM_DAB_ADD_CHUNK.getRemoved()).containsExactly(RDS_UNIQUE_IDENTIFIER);
     }
 
     @Test
     public void describeContents_forChunk() {
-        assertWithMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()).isEqualTo(0);
+        mExpect.withMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents())
+                .isEqualTo(0);
     }
 
     @Test
@@ -276,7 +286,7 @@
 
         ProgramList.Chunk chunkFromParcel =
                 ProgramList.Chunk.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Chunk created from parcel")
+        mExpect.withMessage("Chunk created from parcel")
                 .that(chunkFromParcel).isEqualTo(FM_DAB_ADD_CHUNK);
     }
 
@@ -284,7 +294,7 @@
     public void newArray_forChunkCreator() {
         ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
     }
 
     @Test
@@ -295,7 +305,7 @@
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mRadioTuner.getProgramList(parameters));
 
-        assertWithMessage("Exception for getting program list when not ready")
+        mExpect.withMessage("Exception for getting program list when not ready")
                 .that(thrown).hasMessageThat().contains("Program list is not ready yet");
     }
 
@@ -308,7 +318,7 @@
         RuntimeException thrown = assertThrows(RuntimeException.class,
                 () -> mRadioTuner.getProgramList(parameters));
 
-        assertWithMessage("Exception for getting program list when service is dead")
+        mExpect.withMessage("Exception for getting program list when service is dead")
                 .that(thrown).hasMessageThat().contains("Service died");
     }
 
@@ -330,7 +340,7 @@
 
         ProgramList nullProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
 
-        assertWithMessage("Exception for radio HAL client not supporting program list")
+        mExpect.withMessage("Exception for radio HAL client not supporting program list")
                 .that(nullProgramList).isNull();
     }
 
@@ -344,7 +354,7 @@
             mRadioTuner.getDynamicProgramList(TEST_FILTER);
         });
 
-        assertWithMessage("Exception for radio HAL client service died")
+        mExpect.withMessage("Exception for radio HAL client service died")
                 .that(thrown).hasMessageThat().contains("Service died");
     }
 
@@ -360,7 +370,7 @@
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER);
         verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
-        assertWithMessage("Program info in program list after adding FM and DAB info")
+        mExpect.withMessage("Program info in program list after adding FM and DAB info")
                 .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1,
                         DAB_PROGRAM_INFO_2);
     }
@@ -378,7 +388,7 @@
         mTunerCallback.onProgramListUpdated(fmRemovedChunk);
 
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
-        assertWithMessage("Program info in program list after removing FM id")
+        mExpect.withMessage("Program info in program list after removing FM id")
                 .that(mProgramList.toList()).containsExactly(DAB_PROGRAM_INFO_1,
                         DAB_PROGRAM_INFO_2);
     }
@@ -397,7 +407,7 @@
 
         verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemRemoved(
                 DAB_DMB_SID_EXT_IDENTIFIER);
-        assertWithMessage("Program info in program list after removing part of DAB ids")
+        mExpect.withMessage("Program info in program list after removing part of DAB ids")
                 .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_2);
     }
 
@@ -419,7 +429,7 @@
         mTunerCallback.onProgramListUpdated(dabRemovedChunk2);
 
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER);
-        assertWithMessage("Program info in program list after removing all DAB ids")
+        mExpect.withMessage("Program info in program list after removing all DAB ids")
                 .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO);
     }
 
@@ -448,7 +458,7 @@
 
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER);
-        assertWithMessage("Program list after purge chunk applied")
+        mExpect.withMessage("Program list after purge chunk applied")
                 .that(mProgramList.toList()).isEmpty();
     }
 
@@ -607,6 +617,49 @@
         verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates();
     }
 
+    @Test
+    public void get() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+
+        mExpect.withMessage(
+                "FM program info in program list after updating with chunk of FM program")
+                .that(mProgramList.get(FM_IDENTIFIER)).isEqualTo(FM_PROGRAM_INFO);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getProgramInfos() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER);
+
+        mExpect.withMessage("FM program info in program list")
+                .that(mProgramList.getProgramInfos(FM_IDENTIFIER)).containsExactly(FM_PROGRAM_INFO);
+        mExpect.withMessage("All DAB program info in program list")
+                .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER))
+                .containsExactly(DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getProgramInfos_withIdNotFound() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+
+        mExpect.withMessage("DAB program info in program list")
+                .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER)).isEmpty();
+    }
+
     private static ProgramSelector createProgramSelector(int programType,
             ProgramSelector.Identifier identifier) {
         return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index b9f4c3f..03de143 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -32,8 +32,12 @@
 import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArrayMap;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -42,6 +46,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -90,10 +95,26 @@
     private static final RadioManager.ModuleProperties AMFM_PROPERTIES =
             createAmFmProperties(/* dabFrequencyTable= */ null);
 
+    private static final int DAB_INFO_FLAG_LIVE_VALUE = 1;
+    private static final int DAB_INFO_FLAG_TUNED_VALUE = 1 << 4;
+    private static final int DAB_INFO_FLAG_STEREO_VALUE = 1 << 5;
+    private static final int HD_INFO_FLAG_LIVE_VALUE = 1;
+    private static final int HD_INFO_FLAG_TUNED_VALUE = 1 << 4;
+    private static final int HD_INFO_FLAG_STEREO_VALUE = 1 << 5;
+    private static final int HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE = 1 << 6;
+    private static final int HD_INFO_FLAG_SIS_ACQUISITION_VALUE = 1 << 7;
     /**
-     * Info flags with live, tuned and stereo enabled
+     * Info flags with live, tuned, and stereo enabled for DAB program
      */
-    private static final int INFO_FLAGS = 0b110001;
+    private static final int INFO_FLAGS_DAB = DAB_INFO_FLAG_LIVE_VALUE | DAB_INFO_FLAG_TUNED_VALUE
+            | DAB_INFO_FLAG_STEREO_VALUE;
+    /**
+     * HD program info flags with live, tuned, stereo enabled, signal acquired, SIS information
+     * available but audio unavailable
+     */
+    private static final int INFO_FLAGS_HD = HD_INFO_FLAG_LIVE_VALUE | HD_INFO_FLAG_TUNED_VALUE
+            | HD_INFO_FLAG_STEREO_VALUE | HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE
+            | HD_INFO_FLAG_SIS_ACQUISITION_VALUE;
     private static final int SIGNAL_QUALITY = 2;
     private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
@@ -112,9 +133,20 @@
                     new ProgramSelector.Identifier[]{
                             DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER},
                     /* vendorIds= */ null);
+
+    private static final long HD_FREQUENCY = 97_100;
+    private static final ProgramSelector.Identifier HD_STATION_EXT_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT,
+                    /* value= */ (HD_FREQUENCY << 36) | 0x1L);
+    private static final ProgramSelector HD_SELECTOR = new ProgramSelector(
+            ProgramSelector.PROGRAM_TYPE_FM_HD, HD_STATION_EXT_IDENTIFIER,
+            new ProgramSelector.Identifier[]{}, /* vendorIds= */ null);
+
     private static final RadioMetadata METADATA = createMetadata();
     private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO =
             createDabProgramInfo(DAB_SELECTOR);
+    private static final RadioManager.ProgramInfo HD_PROGRAM_INFO = createHdProgramInfo(
+            HD_SELECTOR);
 
     private static final int EVENT_ANNOUNCEMENT_TYPE = Announcement.TYPE_EVENT;
     private static final List<Announcement> TEST_ANNOUNCEMENT_LIST = Arrays.asList(
@@ -135,6 +167,9 @@
     @Mock
     private ICloseHandle mCloseHandleMock;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void getType_forBandDescriptor() {
         RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -927,6 +962,27 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isSignalAcquired_forProgramInfo() {
+        assertWithMessage("Signal acquisition status for HD program info")
+                .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isHdSisAvailable_forProgramInfo() {
+        assertWithMessage("SIS information acquisition status for HD program")
+                .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isHdAudioAvailable_forProgramInfo() {
+        assertWithMessage("Audio acquisition status for HD program")
+                .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse();
+    }
+
+    @Test
     public void getSignalStrength_forProgramInfo() {
         assertWithMessage("Signal strength of DAB program info")
                 .that(DAB_PROGRAM_INFO.getSignalStrength()).isEqualTo(SIGNAL_QUALITY);
@@ -1156,9 +1212,18 @@
     }
 
     private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
-        return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER,
-                DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
-                SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
+        return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
+                DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED),
+                INFO_FLAGS_DAB, SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
+    }
+
+    private static RadioManager.ProgramInfo createHdProgramInfo(ProgramSelector selector) {
+        long frequency = (selector.getPrimaryId().getValue() >> 32);
+        ProgramSelector.Identifier physicallyTunedToId = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, frequency);
+        return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), physicallyTunedToId,
+                Collections.emptyList(), INFO_FLAGS_HD, SIGNAL_QUALITY, METADATA,
+                /* vendorInfo= */ null);
     }
 
     private void createRadioManager() throws RemoteException {
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index e348a51..3891acc 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -22,12 +22,18 @@
 
 import android.graphics.Bitmap;
 import android.os.Parcel;
+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 org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Arrays;
 import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -35,6 +41,8 @@
 
     private static final int CREATOR_ARRAY_SIZE = 3;
     private static final int INT_KEY_VALUE = 1;
+    private static final String ARTIST_KEY_VALUE = "artistTest";
+    private static final String[] UFIDS_VALUE = new String[]{"ufid1", "ufid2"};
     private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200;
     private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1;
 
@@ -43,6 +51,9 @@
     @Mock
     private Bitmap mBitmapValue;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void describeContents_forClock() {
         RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
@@ -97,7 +108,7 @@
             mBuilder.putInt(invalidIntKey, INT_KEY_VALUE);
         });
 
-        assertWithMessage("Exception for putting illegal int-value key %s", invalidIntKey)
+        assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidIntKey + ".*cannot.*int.*?");
     }
@@ -117,6 +128,42 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void putStringArray_withIllegalKey_throwsException() {
+        String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE);
+        });
+
+        assertWithMessage("Exception for putting illegal string-array-value for key %s",
+                invalidStringArrayKey).that(thrown).hasMessageThat()
+                .matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void putStringArray_withNullKey_throwsException() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+            mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
+        });
+
+        assertWithMessage("Exception for putting string-array with null key")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void putStringArray_withNullString_throwsException() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+            mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
+        });
+
+        assertWithMessage("Exception for putting null string-array")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
     public void containsKey_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
@@ -156,11 +203,10 @@
     @Test
     public void getString_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_ARTIST;
-        String value = "artistTest";
-        RadioMetadata metadata = mBuilder.putString(key, value).build();
+        RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build();
 
         assertWithMessage("String value for key %s in metadata", key)
-                .that(metadata.getString(key)).isEqualTo(value);
+                .that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE);
     }
 
     @Test
@@ -235,10 +281,62 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withKeyInMetadata() {
+        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)
+                .that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withKeyNotInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_UFIDS;
+        RadioMetadata metadata = mBuilder.build();
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            metadata.getStringArray(key);
+        });
+
+        assertWithMessage("Exception for getting string array for string-array value for key %s "
+                + "not in metadata", key).that(thrown).hasMessageThat().contains("not found");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withNullKey() {
+        RadioMetadata metadata = mBuilder.build();
+
+        NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+            metadata.getStringArray(/* key= */ null);
+        });
+
+        assertWithMessage("Exception for getting string array with null key")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withInvalidKey() {
+        String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG;
+        RadioMetadata metadata = mBuilder.build();
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            metadata.getStringArray(invalidClockKey);
+        });
+
+        assertWithMessage("Exception for getting string array for key %s not of string-array type",
+                invalidClockKey).that(thrown).hasMessageThat()
+                .contains("string array");
+    }
+
+    @Test
     public void size_withNonEmptyMetadata() {
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .build();
 
         assertWithMessage("Size of fields in non-empty metadata")
@@ -257,7 +355,7 @@
     public void keySet_withNonEmptyMetadata() {
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue)
                 .build();
 
@@ -291,7 +389,7 @@
     public void equals_forMetadataWithSameContents_returnsTrue() {
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .build();
         RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
         RadioMetadata metadataCopied = copyBuilder.build();
@@ -315,10 +413,29 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void writeToParcel_forRadioMetadata() {
         RadioMetadata metadataExpected = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
+                .build();
+        Parcel parcel = Parcel.obtain();
+
+        metadataExpected.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Radio metadata created from parcel")
+                .that(metadataFromParcel).isEqualTo(metadataExpected);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() {
+        RadioMetadata metadataExpected = mBuilder
+                .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
+                .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, UFIDS_VALUE)
                 .build();
         Parcel parcel = Parcel.obtain();
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 6a6a951..7ca806b 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,9 +36,14 @@
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.RemoteException;
+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 org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -77,6 +83,9 @@
     @Mock
     private RadioTuner.Callback mCallbackMock;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
@@ -604,6 +613,44 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM))
+                .thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
+
+        assertWithMessage("Force analog with feature flag enabled and force FM supported")
+                .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM))
+                .thenReturn(false);
+        when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
+
+        assertWithMessage("Force analog with feature flag enabled but force FM unsupported")
+                .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse();
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
+
+        assertWithMessage("Force analog without Force FM enabled")
+                .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse();
+    }
+
+    @Test
     public void isConfigFlagSet_whenServiceDied_fails() throws Exception {
         when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException());
 
@@ -636,6 +683,43 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+
+        mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
+
+        verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG),
+                anyBoolean());
+        verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG_FM, false);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+        when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM))
+                .thenReturn(false);
+
+        mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
+
+        verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false);
+        verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG_FM),
+                anyBoolean());
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+
+        mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
+
+        verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false);
+    }
+
+    @Test
     public void getParameters_forTunerAdapter() throws Exception {
         List<String> parameterKeys = List.of("ParameterKeyMock");
         Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 824f591..75a7231 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -610,8 +610,6 @@
     @Test
     public void cancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
 
         mTunerSessions[0].cancel();
 
@@ -621,9 +619,6 @@
     @Test
     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
-        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any());
         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
 
         mTunerSessions[0].cancel();
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index 3ec44d1..6edfa02 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -540,8 +540,6 @@
     @Test
     public void cancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
 
         mTunerSessions[0].cancel();
 
@@ -551,9 +549,6 @@
     @Test
     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
-        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any());
         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
 
         mTunerSessions[0].cancel();
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 72969f7..37a499a 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -104,19 +104,15 @@
     private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
 
     private static final Path[] UI_TRACES_PREDUMPED = {
+            Paths.get("/data/misc/perfetto-traces/bugreport/systrace.pftrace"),
             Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"),
             Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
             Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
             Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
             Paths.get("/data/misc/wmtrace/wm_log.winscope"),
-            Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
-            Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
-            Paths.get("/data/misc/wmtrace/transition_trace.winscope"),
+            Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
             Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
     };
-    private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
-            Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
-    };
 
     private Handler mHandler;
     private Executor mExecutor;
@@ -210,7 +206,7 @@
 
         mBrm.preDumpUiData();
         waitTillDumpstateExitedOrTimeout();
-        List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+        List<File> expectedPreDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED);
 
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
         mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
@@ -225,7 +221,6 @@
         assertFdsAreClosed(mBugreportFd);
 
         assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
-        assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
 
         List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
         assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles);
@@ -240,9 +235,9 @@
         // In some corner cases, data dumped as part of the full bugreport could be the same as the
         // pre-dumped data and this test would fail. Hence, here we create fake/artificial
         // pre-dumped data that we know it won't match with the full bugreport data.
-        createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system");
+        createFakeTraceFiles(UI_TRACES_PREDUMPED);
 
-        List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+        List<File> preDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED);
 
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
         mBrm.startBugreport(mBugreportFd, null, full(), mExecutor,
@@ -257,7 +252,6 @@
         assertFdsAreClosed(mBugreportFd);
 
         assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
-        assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
 
         List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
         assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
@@ -480,7 +474,32 @@
         return f;
     }
 
-    private static void startPreDumpedUiTraces() {
+    private static void startPreDumpedUiTraces() throws Exception {
+        // Perfetto traces
+        String perfettoConfig =
+                "buffers: {\n"
+                + "    size_kb: 2048\n"
+                + "    fill_policy: RING_BUFFER\n"
+                + "}\n"
+                + "data_sources: {\n"
+                + "    config {\n"
+                + "        name: \"android.surfaceflinger.transactions\"\n"
+                + "    }\n"
+                + "}\n"
+                + "bugreport_score: 10\n";
+        File tmp = createTempFile("tmp", ".cfg");
+        Files.write(perfettoConfig.getBytes(StandardCharsets.UTF_8), tmp);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "install -m 644 -o root -g root "
+                + tmp.getAbsolutePath() + " /data/misc/perfetto-configs/bugreport-manager-test.cfg"
+        );
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "perfetto --background-wait"
+                + " --config /data/misc/perfetto-configs/bugreport-manager-test.cfg --txt"
+                + " --out /data/misc/perfetto-traces/not-used.perfetto-trace"
+        );
+
+        // Legacy traces
         InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                 "cmd input_method tracing start"
         );
@@ -563,19 +582,24 @@
         return extractedFile;
     }
 
-    private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception {
+    private static void createFakeTraceFiles(Path[] paths) throws Exception {
         File src = createTempFile("fake", ".data");
         Files.write("fake data".getBytes(StandardCharsets.UTF_8), src);
 
         for (Path path : paths) {
             InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                    "install -m 611 -o " + owner + " -g " + owner
-                    + " " + src.getAbsolutePath() + " " + path.toString()
+                    "install -m 644 -o system -g system "
+                    + src.getAbsolutePath() + " " + path.toString()
             );
         }
+
+        // Dumpstate executes "perfetto --save-for-bugreport" as shell
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "chown shell:shell /data/misc/perfetto-traces/bugreport/systrace.pftrace"
+        );
     }
 
-    private static List<File> copyFilesAsRoot(Path[] paths) throws Exception {
+    private static List<File> copyFiles(Path[] paths) throws Exception {
         ArrayList<File> files = new ArrayList<File>();
         for (Path src : paths) {
             File dst = createTempFile(src.getFileName().toString(), ".copy");
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 7f3e014..9430ba6 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -857,7 +857,8 @@
             assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
             assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
             assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
-            assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
+            assertEquals(cDay.getOnTertiaryAccentTextColor(),
+                    cNight.getOnTertiaryAccentTextColor());
             assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
             assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
             assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -1830,7 +1831,7 @@
         assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
-        assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
+        assertThat(c.getOnTertiaryAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getRippleAlpha()).isAtLeast(0x00);
@@ -1848,7 +1849,7 @@
         assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
 
         // The text that is used within the accent color DOES need to have contrast
-        assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
+        assertContrastIsAtLeast(c.getOnTertiaryAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
     }
 
     private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 0f62b1c..930b1a4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -16,14 +16,19 @@
 
 package android.app.servertransaction;
 
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.IDisplayManager;
+import android.os.Handler;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,8 +39,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntConsumer;
-
 /**
  * Tests for {@link ClientTransactionListenerController}.
  *
@@ -47,30 +50,36 @@
 @Presubmit
 public class ClientTransactionListenerControllerTest {
     @Mock
-    private IntConsumer mDisplayChangeListener;
+    private IDisplayManager mIDisplayManager;
+    @Mock
+    private DisplayManager.DisplayListener mListener;
 
+    private DisplayManagerGlobal mDisplayManager;
+    private Handler mHandler;
     private ClientTransactionListenerController mController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mController = spy(ClientTransactionListenerController.createInstanceForTesting());
+        mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
+        mHandler = getInstrumentation().getContext().getMainThreadHandler();
+        mController = spy(ClientTransactionListenerController.createInstanceForTesting(
+                mDisplayManager));
         doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled();
     }
 
     @Test
-    public void testRegisterDisplayChangeListener() {
-        mController.registerDisplayChangeListener(mDisplayChangeListener, Runnable::run);
+    public void testOnDisplayChanged() throws RemoteException {
+        // Mock IDisplayManager to return a display info to trigger display change.
+        final DisplayInfo newDisplayInfo = new DisplayInfo();
+        doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123);
+
+        mDisplayManager.registerDisplayListener(mListener, mHandler,
+                DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
 
         mController.onDisplayChanged(123);
+        mHandler.runWithScissors(() -> { }, 0);
 
-        verify(mDisplayChangeListener).accept(123);
-
-        clearInvocations(mDisplayChangeListener);
-        mController.unregisterDisplayChangeListener(mDisplayChangeListener);
-
-        mController.onDisplayChanged(321);
-
-        verify(mDisplayChangeListener, never()).accept(anyInt());
+        verify(mListener).onDisplayChanged(123);
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 531404b..d10cf16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -62,4 +62,24 @@
         verify(callback2, times(1)).preExecute(clientTransactionHandler);
         verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
     }
+
+    @Test
+    public void testPreExecuteTransactionItems() {
+        final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+        final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        final ClientTransactionHandler clientTransactionHandler =
+                mock(ClientTransactionHandler.class);
+
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(callback1);
+        transaction.addTransactionItem(callback2);
+        transaction.addTransactionItem(stateRequest);
+
+        transaction.preExecute(clientTransactionHandler);
+
+        verify(callback1, times(1)).preExecute(clientTransactionHandler);
+        verify(callback2, times(1)).preExecute(clientTransactionHandler);
+        verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 44a4d58..f2b0f2e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -247,6 +247,31 @@
     }
 
     @Test
+    public void testExecuteTransactionItems_transactionResolution() {
+        ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+        when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
+        ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+        when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+        ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        IBinder token = mock(IBinder.class);
+        when(stateRequest.getActivityToken()).thenReturn(token);
+        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(callback1);
+        transaction.addTransactionItem(callback2);
+        transaction.addTransactionItem(stateRequest);
+
+        transaction.preExecute(mTransactionHandler);
+        mExecutor.execute(transaction);
+
+        InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+        inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
+        inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
+        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+    }
+
+    @Test
     public void testDoNotLaunchDestroyedActivity() {
         final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
         when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
@@ -279,12 +304,43 @@
     }
 
     @Test
+    public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+        final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
+        when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+        // Assume launch transaction is still in queue, so there is no client record.
+        when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+        // An incoming destroy transaction enters binder thread (preExecute).
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
+        destroyTransaction.addTransactionItem(
+                DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+        destroyTransaction.preExecute(mTransactionHandler);
+        // The activity should be added to to-be-destroyed container.
+        assertEquals(1, activitiesToBeDestroyed.size());
+
+        // A previous queued launch transaction runs on main thread (execute).
+        final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
+        final LaunchActivityItem launchItem =
+                spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+        launchTransaction.addTransactionItem(launchItem);
+        mExecutor.execute(launchTransaction);
+
+        // The launch transaction should not be executed because its token is in the
+        // to-be-destroyed container.
+        verify(launchItem, never()).execute(any(), any());
+
+        // After the destroy transaction has been executed, the token should be removed.
+        mExecutor.execute(destroyTransaction);
+        assertTrue(activitiesToBeDestroyed.isEmpty());
+    }
+
+    @Test
     public void testActivityResultRequiredStateResolution() {
         when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
 
         PostExecItem postExecItem = new PostExecItem(ON_RESUME);
 
-        IBinder token = mock(IBinder.class);
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addCallback(postExecItem);
 
@@ -300,6 +356,26 @@
     }
 
     @Test
+    public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+        when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
+        PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(postExecItem);
+
+        // Verify resolution that should get to onPause
+        mClientRecord.setState(ON_RESUME);
+        mExecutor.executeTransactionItems(transaction);
+        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+
+        // Verify resolution that should get to onStart
+        mClientRecord.setState(ON_STOP);
+        mExecutor.executeTransactionItems(transaction);
+        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+    }
+
+    @Test
     public void testClosestStateResolutionForSameState() {
         final int[] allStates = new int[] {
                 ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
@@ -444,6 +520,18 @@
         mExecutor.executeCallbacks(transaction);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(activityItem);
+        when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+        mExecutor.executeTransactionItems(transaction);
+    }
+
     @Test
     public void testActivityItemExecute() {
         final IBinder token = mock(IBinder.class);
@@ -464,6 +552,26 @@
         inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
     }
 
+    @Test
+    public void testExecuteTransactionItems_activityItemExecute() {
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        when(activityItem.getActivityToken()).thenReturn(token);
+        transaction.addTransactionItem(activityItem);
+        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        transaction.addTransactionItem(stateRequest);
+        when(stateRequest.getActivityToken()).thenReturn(token);
+        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+        mExecutor.execute(transaction);
+
+        final InOrder inOrder = inOrder(activityItem, stateRequest);
+        inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+    }
+
     private static int[] shuffledArray(int[] inputArray) {
         final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
         Collections.shuffle(list);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 7d047c9..4aa62c5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -285,6 +285,10 @@
                 78 /* configChanges */);
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(callback1);
+        transaction.addTransactionItem(callback2);
+        transaction.addTransactionItem(lifecycleRequest);
+
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
         transaction.setLifecycleStateRequest(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
new file mode 100644
index 0000000..c00eb91
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowStateResizeItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowStateResizeItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowStateResizeItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IWindow mWindow;
+    @Mock
+    private ClientWindowFrames mFrames;
+    @Mock
+    private MergedConfiguration mConfiguration;
+    @Mock
+    private InsetsState mInsetsState;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testExecute() throws RemoteException {
+        final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+                true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+        item.execute(mHandler, mPendingActions);
+
+        verify(mWindow).resized(mFrames,
+                true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
new file mode 100644
index 0000000..2ec58d4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.usage;
+
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.app.usage.UsageEvents.Event;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ParcelableUsageEventListTest {
+    private static final int SMALL_TEST_EVENT_COUNT = 100;
+    private static final int LARGE_TEST_EVENT_COUNT = 10000;
+
+    private Random mRandom = new Random();
+
+    @Test
+    public void testSmallList() throws Exception {
+        testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT);
+    }
+
+    @Test
+    public void testLargeList() throws Exception {
+        testParcelableUsageEventList(LARGE_TEST_EVENT_COUNT);
+    }
+
+    private void testParcelableUsageEventList(int eventCount) throws Exception {
+        List<Event> smallList = new ArrayList<>();
+        for (int i = 0; i < eventCount; i++) {
+            smallList.add(generateUsageEvent());
+        }
+
+        ParcelableUsageEventList slice;
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0);
+            parcel.setDataPosition(0);
+            slice = parcel.readParcelable(getClass().getClassLoader(),
+                    ParcelableUsageEventList.class);
+        } finally {
+            parcel.recycle();
+        }
+
+        assertNotNull(slice);
+        assertNotNull(slice.getList());
+        assertEquals(eventCount, slice.getList().size());
+
+        for (int i = 0; i < eventCount; i++) {
+            compareUsageEvent(smallList.get(i), slice.getList().get(i));
+        }
+    }
+
+    private Event generateUsageEvent() {
+        final Event event = new Event();
+        event.mEventType = mRandom.nextInt(Event.MAX_EVENT_TYPE + 1);
+        event.mPackage = anyString();
+        event.mClass = anyString();
+        event.mTimeStamp = anyLong();
+        event.mInstanceId = anyInt();
+        event.mTimeStamp = anyLong();
+
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE:
+                event.mConfiguration = new Configuration();
+                event.mConfiguration.seq = anyInt();
+                event.mConfiguration.screenLayout = Configuration.SCREENLAYOUT_ROUND_YES;
+                event.mConfiguration.smallestScreenWidthDp = 100;
+                event.mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+                event.mConfiguration.windowConfiguration.setRotation(ROTATION_90);
+                break;
+            case Event.SHORTCUT_INVOCATION:
+                event.mShortcutId = anyString();
+                break;
+            case Event.CHOOSER_ACTION:
+                event.mAction = anyString();
+                event.mContentType = anyString();
+                event.mContentAnnotations = new String[mRandom.nextInt(10)];
+                for (int i = 0; i < event.mContentAnnotations.length; i++) {
+                    event.mContentAnnotations[i] = anyString();
+                }
+                break;
+            case Event.STANDBY_BUCKET_CHANGED:
+                event.mBucketAndReason = anyInt();
+                break;
+            case Event.NOTIFICATION_INTERRUPTION:
+                event.mNotificationChannelId = anyString();
+                break;
+            case Event.LOCUS_ID_SET:
+                event.mLocusId = anyString();
+                break;
+        }
+
+        event.mFlags = anyInt();
+        return event;
+    }
+
+    private static void compareUsageEvent(Event ue1, Event ue2) {
+        assertEquals(ue1.mPackage, ue2.mPackage);
+        assertEquals(ue1.mClass, ue2.mClass);
+        assertEquals(ue1.mTaskRootPackage, ue2.mTaskRootPackage);
+        assertEquals(ue1.mTaskRootClass, ue2.mTaskRootClass);
+        assertEquals(ue1.mInstanceId, ue2.mInstanceId);
+        assertEquals(ue1.mEventType, ue2.mEventType);
+        assertEquals(ue1.mTimeStamp, ue2.mTimeStamp);
+
+        switch (ue1.mEventType) {
+            case Event.CONFIGURATION_CHANGE:
+                assertEquals(ue1.mConfiguration, ue2.mConfiguration);
+                break;
+            case Event.SHORTCUT_INVOCATION:
+                assertEquals(ue1.mShortcutId, ue2.mShortcutId);
+                break;
+            case Event.CHOOSER_ACTION:
+                assertEquals(ue1.mAction, ue2.mAction);
+                assertEquals(ue1.mContentType, ue2.mContentType);
+                assertTrue(Arrays.equals(ue1.mContentAnnotations, ue2.mContentAnnotations));
+                break;
+            case Event.STANDBY_BUCKET_CHANGED:
+                assertEquals(ue1.mBucketAndReason, ue2.mBucketAndReason);
+                break;
+            case Event.NOTIFICATION_INTERRUPTION:
+                assertEquals(ue1.mNotificationChannelId, ue1.mNotificationChannelId);
+                break;
+            case Event.LOCUS_ID_SET:
+                assertEquals(ue1.mLocusId, ue2.mLocusId);
+                break;
+        }
+
+        assertEquals(ue1.mFlags, ue2.mFlags);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/BrickDeniedTest.java b/core/tests/coretests/src/android/content/BrickDeniedTest.java
deleted file mode 100644
index d8c9baa..0000000
--- a/core/tests/coretests/src/android/content/BrickDeniedTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.test.AndroidTestCase;
-
-import androidx.test.filters.SmallTest;
-
-/** Test to make sure brick intents <b>don't</b> work without permission. */
-public class BrickDeniedTest extends AndroidTestCase {
-    @SmallTest
-    public void testBrick() {
-        // Try both the old and new brick intent names.  Neither should work,
-        // since this test application doesn't have the required permission.
-        // If it does work, well, the test certainly won't pass.
-        getContext().sendBroadcast(new Intent("SHES_A_BRICK_HOUSE"));
-        getContext().sendBroadcast(new Intent("android.intent.action.BRICK"));
-    }
-}
diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
index f8348d2..eefa6e4 100644
--- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
+++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
@@ -32,6 +32,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.List;
+
 /**
  * Unit test for {@link ContentCaptureOptions}.
  *
@@ -44,6 +46,9 @@
     private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo");
     private static final ComponentName COMPONENT1 = new ComponentName("comp", "one");
     private static final ComponentName COMPONENT2 = new ComponentName("two", "comp");
+    private static final List<List<String>> CONTENT_PROTECTION_REQUIRED_GROUPS =
+            List.of(List.of("first"), List.of("second", "third"), List.of());
+    private static final List<List<String>> CONTENT_PROTECTION_OPTIONAL_GROUPS = List.of();
     private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS =
             new ContentCaptureOptions(
                     /* loggingLevel= */ 1000,
@@ -55,7 +60,10 @@
                     /* enableReceiver= */ false,
                     new ContentCaptureOptions.ContentProtectionOptions(
                             /* enableReceiver= */ true,
-                            /* bufferSize= */ 2001),
+                            /* bufferSize= */ 2001,
+                            CONTENT_PROTECTION_REQUIRED_GROUPS,
+                            CONTENT_PROTECTION_OPTIONAL_GROUPS,
+                            /* optionalGroupsThreshold= */ 2002),
                     /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2));
 
     @Mock private Context mContext;
@@ -134,6 +142,19 @@
                         .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver)
                         .append(", bufferSize=")
                         .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize)
+                        .append(", requiredGroupsSize=")
+                        .append(
+                                CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups
+                                        .size())
+                        .append(", optionalGroupsSize=")
+                        .append(
+                                CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups
+                                        .size())
+                        .append(", optionalGroupsThreshold=")
+                        .append(
+                                CONTENT_CAPTURE_OPTIONS
+                                        .contentProtectionOptions
+                                        .optionalGroupsThreshold)
                         .append("], whitelisted=")
                         .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents)
                         .append(']')
@@ -166,6 +187,15 @@
                 .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver);
         assertThat(actual.contentProtectionOptions.bufferSize)
                 .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize);
+        assertThat(actual.contentProtectionOptions.requiredGroups)
+                .containsExactlyElementsIn(
+                        CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups);
+        assertThat(actual.contentProtectionOptions.optionalGroups)
+                .containsExactlyElementsIn(
+                        CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups);
+        assertThat(actual.contentProtectionOptions.optionalGroupsThreshold)
+                .isEqualTo(
+                        CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroupsThreshold);
         assertThat(actual.whitelistedComponents)
                 .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents);
     }
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 0941a2b..6c14ee3 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@
             );
         });
 
-        PollingCheck.waitFor(/* timeout= */ 7000, () -> {
+        PollingCheck.waitFor(/* timeout= */ 10000, () -> {
             AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
             rule.getScenario().onActivity(activity ->
                     isActivityAtCorrectScale.set(
@@ -163,7 +163,7 @@
         });
 
         PollingCheck.waitFor(
-                /* timeout= */ 5000,
+                /* timeout= */ 10000,
                 () -> InstrumentationRegistry.getInstrumentation()
                                         .getContext()
                                         .getResources()
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3f78396..0d687b2 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK;
+import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -931,7 +931,7 @@
         return String.format(xml, op, lang, font);
     }
 
-    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_prepend() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -947,7 +947,7 @@
         assertB3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_replace() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -963,7 +963,7 @@
         assertB3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_append() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -979,7 +979,7 @@
         assertA3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -995,7 +995,7 @@
         assertA3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 149f58f..969ae8e 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -16,12 +16,19 @@
 
 package android.hardware.display;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -37,13 +44,20 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+/**
+ * Tests for {@link DisplayManagerGlobal}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:DisplayManagerGlobalTest
+ */
+@Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerGlobalTest {
 
     private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
 
     @Mock
     private IDisplayManager mDisplayManager;
@@ -51,6 +65,9 @@
     @Mock
     private DisplayManager.DisplayListener mListener;
 
+    @Mock
+    private DisplayManager.DisplayListener mListener2;
+
     @Captor
     private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor;
 
@@ -69,7 +86,8 @@
 
     @Test
     public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+                null);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -81,7 +99,11 @@
         Mockito.verifyNoMoreInteractions(mListener);
 
         Mockito.reset(mListener);
-        callback.onDisplayEvent(1, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+        // Mock IDisplayManager to return a different display info to trigger display change.
+        final DisplayInfo newDisplayInfo = new DisplayInfo();
+        newDisplayInfo.rotation++;
+        doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId);
+        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         waitForHandler();
         Mockito.verify(mListener).onDisplayChanged(eq(displayId));
         Mockito.verifyNoMoreInteractions(mListener);
@@ -97,26 +119,27 @@
     public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
         // First we subscribe to all events in order to test that the subsequent calls to
         // registerDisplayListener will update the event mask.
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+                null);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
 
         int displayId = 1;
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED);
+                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
@@ -139,7 +162,7 @@
     public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
             throws RemoteException {
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);
         InOrder inOrder = Mockito.inOrder(mDisplayManager);
 
         inOrder.verify(mDisplayManager)
@@ -159,10 +182,48 @@
         mDisplayManagerGlobal.unregisterDisplayListener(mListener);
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+    }
 
+    @Test
+    public void testHandleDisplayChangeFromWindowManager() throws RemoteException {
+        // Mock IDisplayManager to return a display info to trigger display change.
+        final DisplayInfo newDisplayInfo = new DisplayInfo();
+        doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(123);
+        doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(321);
+
+        // Nothing happens when there is no listener.
+        mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(123);
+
+        // One listener listens on add/remove, and the other one listens on change.
+        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+                DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */);
+        mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler,
+                DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
+
+        mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
+        waitForHandler();
+
+        verify(mListener, never()).onDisplayChanged(anyInt());
+        verify(mListener2).onDisplayChanged(321);
+
+        // Trigger the callback again even if the display info is not changed.
+        clearInvocations(mListener2);
+        mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
+        waitForHandler();
+
+        verify(mListener2).onDisplayChanged(321);
+
+        // No callback for non-existing display (no display info returned from IDisplayManager).
+        clearInvocations(mListener2);
+        mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(456);
+        waitForHandler();
+
+        verify(mListener2, never()).onDisplayChanged(anyInt());
     }
 
     private void waitForHandler() {
-        mHandler.runWithScissors(() -> { }, 0);
+        mHandler.runWithScissors(() -> {
+        }, 0);
     }
 }
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 07dec5d..b843ad7 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -79,6 +79,8 @@
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
+    @Mock
+    private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
 
     @Captor
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor;
@@ -191,6 +193,23 @@
                 any(), anyString(), any(), any(), anyBoolean());
     }
 
+    @Test
+    public void detectClient_onError() throws RemoteException {
+        ArgumentCaptor<IFaceServiceReceiver> argumentCaptor =
+                ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback,
+                new FaceAuthenticateOptions.Builder().build());
+
+        verify(mService).detectFace(any(), argumentCaptor.capture(), any());
+
+        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(mFaceDetectionCallback).onDetectionError(anyInt());
+    }
+
     private void initializeProperties() throws RemoteException {
         verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
 
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 625e2e3..70313b8 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -74,6 +74,8 @@
     private FingerprintManager.AuthenticationCallback mAuthCallback;
     @Mock
     private FingerprintManager.EnrollmentCallback mEnrollCallback;
+    @Mock
+    private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor;
@@ -166,4 +168,21 @@
                 anyString());
         verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
     }
+
+    @Test
+    public void detectClient_onError() throws RemoteException {
+        ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor =
+                ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+        mFingerprintManager.detectFingerprint(new CancellationSignal(),
+                mFingerprintDetectionCallback,
+                new FingerprintAuthenticateOptions.Builder().build());
+
+        verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any());
+
+        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
+    }
 }
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a..88fc826 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@
         // restore the original values
         LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
     }
+
+    @SmallTest
+    public void testIntersection() {
+        LocaleList localesWithN = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.ITALIAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.JAPAN,
+                Locale.CANADA,
+                Locale.CANADA_FRENCH);
+        LocaleList localesWithE = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.CANADA_FRENCH);
+        LocaleList localesWithNAndE = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.CANADA_FRENCH);
+
+        assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN)));
+    }
 }
diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
deleted file mode 100644
index e1f9523..0000000
--- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
+++ /dev/null
@@ -1,126 +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.os.health;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.ConditionVariable;
-import android.os.PowerMonitor;
-import android.os.PowerMonitorReadings;
-
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemHealthManagerTest {
-    private List<PowerMonitor> mPowerMonitorInfo;
-    private PowerMonitorReadings mReadings;
-    private RuntimeException mException;
-
-    @Test
-    public void getPowerMonitors() {
-        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
-        List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors();
-        assertThat(powerMonitorInfo).isNotNull();
-        if (powerMonitorInfo.isEmpty()) {
-            // This device does not support PowerStats HAL
-            return;
-        }
-
-        PowerMonitor consumerMonitor = null;
-        PowerMonitor measurementMonitor = null;
-        for (PowerMonitor pmi : powerMonitorInfo) {
-            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
-                measurementMonitor = pmi;
-            } else {
-                consumerMonitor = pmi;
-            }
-        }
-
-        List<PowerMonitor> selectedMonitors = new ArrayList<>();
-        if (consumerMonitor != null) {
-            selectedMonitors.add(consumerMonitor);
-        }
-        if (measurementMonitor != null) {
-            selectedMonitors.add(measurementMonitor);
-        }
-
-        PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
-
-        for (PowerMonitor monitor : selectedMonitors) {
-            assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
-            assertThat(readings.getTimestamp(monitor)).isGreaterThan(0);
-        }
-    }
-
-    @Test
-    public void getPowerMonitorsAsync() {
-        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
-        ConditionVariable done = new ConditionVariable();
-        shm.getSupportedPowerMonitors(null, pms -> {
-            mPowerMonitorInfo = pms;
-            done.open();
-        });
-        done.block();
-        assertThat(mPowerMonitorInfo).isNotNull();
-        if (mPowerMonitorInfo.isEmpty()) {
-            // This device does not support PowerStats HAL
-            return;
-        }
-
-        PowerMonitor consumerMonitor = null;
-        PowerMonitor measurementMonitor = null;
-        for (PowerMonitor pmi : mPowerMonitorInfo) {
-            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
-                measurementMonitor = pmi;
-            } else {
-                consumerMonitor = pmi;
-            }
-        }
-
-        List<PowerMonitor> selectedMonitors = new ArrayList<>();
-        if (consumerMonitor != null) {
-            selectedMonitors.add(consumerMonitor);
-        }
-        if (measurementMonitor != null) {
-            selectedMonitors.add(measurementMonitor);
-        }
-
-        done.close();
-        shm.getPowerMonitorReadings(selectedMonitors, null,
-                readings -> {
-                    mReadings = readings;
-                    done.open();
-                },
-                exception -> {
-                    mException = exception;
-                    done.open();
-                }
-        );
-        done.block();
-
-        assertThat(mException).isNull();
-
-        for (PowerMonitor monitor : selectedMonitors) {
-            assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0);
-            assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 55ded9c..0855268 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -16,7 +16,9 @@
 
 package android.service.notification;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -24,20 +26,36 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.spy;
+
+import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
 import android.os.Parcel;
+import android.os.SharedMemory;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.testing.TestableContext;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-
-import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @RunWith(Parameterized.class)
 public class NotificationRankingUpdateTest {
@@ -47,6 +65,9 @@
 
     private NotificationChannel mNotificationChannel;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     // TODO(b/284297289): remove this flag set once resolved.
     @Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
     public static Boolean[] getRankingUpdateAshmem() {
@@ -56,26 +77,364 @@
     @Parameterized.Parameter
     public boolean mRankingUpdateAshmem;
 
+    @Rule
+    public TestableContext mContext =
+            spy(new TestableContext(InstrumentationRegistry.getContext(), null));
+
+    protected TestableContext getContext() {
+        return mContext;
+    }
+
+    public static String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
+
+    /**
+     * Creates a NotificationRankingUpdate with prepopulated Ranking entries
+     * @param context A testable context, used for PendingIntent creation
+     * @return The NotificationRankingUpdate to be used as test data
+     */
+    public static NotificationRankingUpdate generateUpdate(TestableContext context) {
+        NotificationListenerService.Ranking[] rankings =
+                new NotificationListenerService.Ranking[mKeys.length];
+        for (int i = 0; i < mKeys.length; i++) {
+            final String key = mKeys[i];
+            NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+            ranking.populate(
+                    key,
+                    i,
+                    !isIntercepted(i),
+                    getVisibilityOverride(i),
+                    getSuppressedVisualEffects(i),
+                    getImportance(i),
+                    getExplanation(key),
+                    getOverrideGroupKey(key),
+                    getChannel(key, i),
+                    getPeople(key, i),
+                    getSnoozeCriteria(key, i),
+                    getShowBadge(i),
+                    getUserSentiment(i),
+                    getHidden(i),
+                    lastAudiblyAlerted(i),
+                    getNoisy(i),
+                    getSmartActions(key, i, context),
+                    getSmartReplies(key, i),
+                    canBubble(i),
+                    isTextChanged(i),
+                    isConversation(i),
+                    getShortcutInfo(i),
+                    getRankingAdjustment(i),
+                    isBubble(i),
+                    getProposedImportance(i),
+                    hasSensitiveContent(i)
+            );
+            rankings[i] = ranking;
+        }
+        return new NotificationRankingUpdate(rankings);
+    }
+
+    /**
+     * Produces a visibility override value based on the provided index.
+     */
+    public static int getVisibilityOverride(int index) {
+        return index * 9;
+    }
+
+    /**
+     * Produces a group key based on the provided key.
+     */
+    public static String getOverrideGroupKey(String key) {
+        return key + key;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isIntercepted, based on the provided index.
+     */
+    public static boolean isIntercepted(int index) {
+        return index % 2 == 0;
+    }
+
+    /**
+     * Produces a suppressed visual effects value based on the provided index
+     */
+    public static int getSuppressedVisualEffects(int index) {
+        return index * 2;
+    }
+
+    /**
+     * Produces an importance value, based on the provided index
+     */
+    public static int getImportance(int index) {
+        return index;
+    }
+
+    /**
+     * Produces an explanation value, based on the provided key
+     */
+    public static String getExplanation(String key) {
+        return key + "explain";
+    }
+
+    /**
+     * Produces a notification channel, based on the provided key and index
+     */
+    public static NotificationChannel getChannel(String key, int index) {
+        return new NotificationChannel(key, key, getImportance(index));
+    }
+
+    /**
+     * Produces a boolean that can be used to represent showBadge, based on the provided index
+     */
+    public static boolean getShowBadge(int index) {
+        return index % 3 == 0;
+    }
+
+    /**
+     * Produces a user sentiment value, based on the provided index
+     */
+    public static int getUserSentiment(int index) {
+        switch(index % 3) {
+            case 0:
+                return USER_SENTIMENT_NEGATIVE;
+            case 1:
+                return USER_SENTIMENT_NEUTRAL;
+            case 2:
+                return USER_SENTIMENT_POSITIVE;
+        }
+        return USER_SENTIMENT_NEUTRAL;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent "hidden," based on the provided index.
+     */
+    public static boolean getHidden(int index) {
+        return index % 2 == 0;
+    }
+
+    /**
+     * Produces a long to represent lastAudiblyAlerted based on the provided index.
+     */
+    public static long lastAudiblyAlerted(int index) {
+        return index * 2000L;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent "noisy," based on the provided index.
+     */
+    public static boolean getNoisy(int index) {
+        return index < 1;
+    }
+
+    /**
+     * Produces strings that can be used to represent people, based on the provided key and index.
+     */
+    public static ArrayList<String> getPeople(String key, int index) {
+        ArrayList<String> people = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            people.add(i + key);
+        }
+        return people;
+    }
+
+    /**
+     * Produces a number of snoozeCriteria, based on the provided key and index.
+     */
+    public static ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) {
+        ArrayList<SnoozeCriterion> snooze = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key));
+        }
+        return snooze;
+    }
+
+    /**
+     * Produces a list of Actions which can be used to represent smartActions.
+     * These actions are built from pending intents with intent titles based on the provided
+     * key, and ids based on the provided index.
+     */
+    public static ArrayList<Notification.Action> getSmartActions(String key,
+                                                                 int index,
+                                                                 TestableContext context) {
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            PendingIntent intent = PendingIntent.getBroadcast(
+                    context,
+                    index /*requestCode*/,
+                    new Intent("ACTION_" + key),
+                    PendingIntent.FLAG_IMMUTABLE /*flags*/);
+            actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build());
+        }
+        return actions;
+    }
+
+    /**
+     * Produces index number of "smart replies," all based on the provided key and index
+     */
+    public static ArrayList<CharSequence> getSmartReplies(String key, int index) {
+        ArrayList<CharSequence> choices = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            choices.add("choice_" + key + "_" + i);
+        }
+        return choices;
+    }
+
+    /**
+     * Produces a boolean that can be  used to represent canBubble, based on the provided index
+     */
+    public static boolean canBubble(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isTextChanged, based on the provided index.
+     */
+    public static boolean isTextChanged(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isConversation, based on the provided index.
+     */
+    public static boolean isConversation(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Produces a ShortcutInfo value based on the provided index.
+     */
+    public static ShortcutInfo getShortcutInfo(int index) {
+        ShortcutInfo si = new ShortcutInfo(
+                index, String.valueOf(index), "packageName", new ComponentName("1", "1"), null,
+                "title", 0, "titleResName", "text", 0, "textResName",
+                "disabledMessage", 0, "disabledMessageResName",
+                null, null, 0, null, 0, 0,
+                0, "iconResName", "bitmapPath", null, 0,
+                null, null, null, null);
+        return si;
+    }
+
+    /**
+     * Produces a rankingAdjustment value, based on the provided index.
+     */
+    public static int getRankingAdjustment(int index) {
+        return index % 3 - 1;
+    }
+
+    /**
+     * Produces a proposedImportance, based on the provided index.
+     */
+    public static int getProposedImportance(int index) {
+        return index % 5 - 1;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent hasSensitiveContent, based on the provided
+     * index.
+     */
+    public static boolean hasSensitiveContent(int index) {
+        return index % 3 == 0;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isBubble, based on the provided index.
+     */
+    public static boolean isBubble(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Checks that each of the pairs of actions in the two provided lists has identical titles,
+     * and that the lists have the same number of elements.
+     */
+    public void assertActionsEqual(
+            List<Notification.Action> expecteds, List<Notification.Action> actuals) {
+        Assert.assertEquals(expecteds.size(), actuals.size());
+        for (int i = 0; i < expecteds.size(); i++) {
+            Notification.Action expected = expecteds.get(i);
+            Notification.Action actual = actuals.get(i);
+            Assert.assertEquals(expected.title.toString(), actual.title.toString());
+        }
+    }
+
+    /**
+     * Checks that all subelements of the provided NotificationRankingUpdates are equal.
+     */
+    public void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) {
+        detailedAssertEquals(a.getRankingMap(), b.getRankingMap());
+    }
+
+    /**
+     * Checks that all subelements of the provided Ranking objects are equal.
+     */
+    public void detailedAssertEquals(String comment, NotificationListenerService.Ranking a,
+                                     NotificationListenerService.Ranking b) {
+        Assert.assertEquals(comment, a.getKey(), b.getKey());
+        Assert.assertEquals(comment, a.getRank(), b.getRank());
+        Assert.assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter());
+        Assert.assertEquals(comment, a.getLockscreenVisibilityOverride(),
+                b.getLockscreenVisibilityOverride());
+        Assert.assertEquals(comment, a.getSuppressedVisualEffects(),
+                b.getSuppressedVisualEffects());
+        Assert.assertEquals(comment, a.getImportance(), b.getImportance());
+        Assert.assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation());
+        Assert.assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey());
+        Assert.assertEquals(comment, a.getChannel().toString(), b.getChannel().toString());
+        Assert.assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople());
+        Assert.assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria());
+        Assert.assertEquals(comment, a.canShowBadge(), b.canShowBadge());
+        Assert.assertEquals(comment, a.getUserSentiment(), b.getUserSentiment());
+        Assert.assertEquals(comment, a.isSuspended(), b.isSuspended());
+        Assert.assertEquals(comment, a.getLastAudiblyAlertedMillis(),
+                b.getLastAudiblyAlertedMillis());
+        Assert.assertEquals(comment, a.isNoisy(), b.isNoisy());
+        Assert.assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
+        Assert.assertEquals(comment, a.canBubble(), b.canBubble());
+        Assert.assertEquals(comment, a.isConversation(), b.isConversation());
+        if (a.getConversationShortcutInfo() != null && b.getConversationShortcutInfo() != null) {
+            Assert.assertEquals(comment, a.getConversationShortcutInfo().getId(),
+                    b.getConversationShortcutInfo().getId());
+        } else {
+            // One or both must be null, so we can check for equality.
+            Assert.assertEquals(a.getConversationShortcutInfo(), b.getConversationShortcutInfo());
+        }
+        assertActionsEqual(a.getSmartActions(), b.getSmartActions());
+        Assert.assertEquals(a.getProposedImportance(), b.getProposedImportance());
+        Assert.assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent());
+    }
+
+    /**
+     * Checks that the two RankingMaps have identical keys, and that each Ranking object for
+     * each of those keys is identical.
+     */
+    public void detailedAssertEquals(NotificationListenerService.RankingMap a,
+                                     NotificationListenerService.RankingMap b) {
+        NotificationListenerService.Ranking arank = new NotificationListenerService.Ranking();
+        NotificationListenerService.Ranking brank = new NotificationListenerService.Ranking();
+        assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys());
+        for (String key : a.getOrderedKeys()) {
+            a.getRanking(key, arank);
+            b.getRanking(key, brank);
+            detailedAssertEquals("ranking for key <" + key + ">", arank, brank);
+        }
+    }
+
     @Before
     public void setUp() {
         mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
-            if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
-                return mRankingUpdateAshmem;
-            }
-            return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
-        };
+        if (mRankingUpdateAshmem) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+        }
     }
 
-    @After
-    public void tearDown() {
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
-    }
-
-    public NotificationListenerService.Ranking createTestRanking(String key, int rank) {
+    /**
+     * Creates a mostly empty Test Ranking object with the specified key, rank, and smartActions.
+     */
+    public NotificationListenerService.Ranking createEmptyTestRanking(
+            String key, int rank, ArrayList<Notification.Action> actions) {
         NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+
         ranking.populate(
                 /* key= */ key,
                 /* rank= */ rank,
@@ -93,7 +452,7 @@
                 /* hidden= */ false,
                 /* lastAudiblyAlertedMs= */ -1,
                 /* noisy= */ false,
-                /* smartActions= */ null,
+                /* smartActions= */ actions,
                 /* smartReplies= */ null,
                 /* canBubble= */ false,
                 /* isTextChanged= */ false,
@@ -107,54 +466,110 @@
         return ranking;
     }
 
+    // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking.
     @Test
-    public void testRankingUpdate_rankingConstructor() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
+    public void testRankingUpdate_parcel() {
+        NotificationRankingUpdate nru = generateUpdate(getContext());
+        Parcel parcel = Parcel.obtain();
+        nru.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
+        // The rankingUpdate file descriptor is only non-null in the new path.
+        if (Flags.rankingUpdateAshmem()) {
+            assertTrue(nru1.isFdNotNullAndClosed());
+        }
+        detailedAssertEquals(nru, nru1);
+        parcel.recycle();
+    }
 
-        NotificationListenerService.RankingMap retrievedRankings = rankingUpdate.getRankingMap();
-        NotificationListenerService.Ranking retrievedRanking =
-                new NotificationListenerService.Ranking();
-        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
-        assertEquals(123, retrievedRanking.getRank());
+    // Tests parceling of RankingMap and RankingMap.equals
+    @Test
+    public void testRankingMap_parcel() {
+        NotificationListenerService.RankingMap rmap = generateUpdate(getContext()).getRankingMap();
+        Parcel parcel = Parcel.obtain();
+        rmap.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationListenerService.RankingMap rmap1 =
+                NotificationListenerService.RankingMap.CREATOR.createFromParcel(parcel);
+
+        detailedAssertEquals(rmap, rmap1);
+        Assert.assertEquals(rmap, rmap1);
+        parcel.recycle();
+    }
+
+    // Tests parceling of Ranking and Ranking.equals
+    @Test
+    public void testRanking_parcel() {
+        NotificationListenerService.Ranking ranking =
+                generateUpdate(getContext()).getRankingMap().getRawRankingObject(mKeys[0]);
+        Parcel parcel = Parcel.obtain();
+        ranking.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationListenerService.Ranking ranking1 =
+                new NotificationListenerService.Ranking(parcel);
+        detailedAssertEquals("rankings differ: ", ranking, ranking1);
+        Assert.assertEquals(ranking, ranking1);
+        parcel.recycle();
+    }
+
+    // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking.
+    @Test
+    public void testRankingUpdate_equals_legacy() {
+        NotificationRankingUpdate nru = generateUpdate(getContext());
+        NotificationRankingUpdate nru2 = generateUpdate(getContext());
+        detailedAssertEquals(nru, nru2);
+        Assert.assertEquals(nru, nru2);
+        NotificationListenerService.Ranking tweak =
+                nru2.getRankingMap().getRawRankingObject(mKeys[0]);
+        tweak.populate(
+                tweak.getKey(),
+                tweak.getRank(),
+                !tweak.matchesInterruptionFilter(), // note the inversion here!
+                tweak.getLockscreenVisibilityOverride(),
+                tweak.getSuppressedVisualEffects(),
+                tweak.getImportance(),
+                tweak.getImportanceExplanation(),
+                tweak.getOverrideGroupKey(),
+                tweak.getChannel(),
+                (ArrayList) tweak.getAdditionalPeople(),
+                (ArrayList) tweak.getSnoozeCriteria(),
+                tweak.canShowBadge(),
+                tweak.getUserSentiment(),
+                tweak.isSuspended(),
+                tweak.getLastAudiblyAlertedMillis(),
+                tweak.isNoisy(),
+                (ArrayList) tweak.getSmartActions(),
+                (ArrayList) tweak.getSmartReplies(),
+                tweak.canBubble(),
+                tweak.isTextChanged(),
+                tweak.isConversation(),
+                tweak.getConversationShortcutInfo(),
+                tweak.getRankingAdjustment(),
+                tweak.isBubble(),
+                tweak.getProposedImportance(),
+                tweak.hasSensitiveContent()
+        );
+        assertNotEquals(nru, nru2);
     }
 
     @Test
-    public void testRankingUpdate_parcelConstructor() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
+    public void testRankingUpdate_rankingConstructor() {
+        NotificationRankingUpdate nru = generateUpdate(getContext());
+        NotificationRankingUpdate constructedNru = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{
+                        nru.getRankingMap().getRawRankingObject(mKeys[0]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[1]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[2]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[3]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[4])
+                });
 
-        Parcel parceledRankingUpdate = Parcel.obtain();
-        rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
-        parceledRankingUpdate.setDataPosition(0);
-
-        NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
-                parceledRankingUpdate);
-
-        NotificationListenerService.RankingMap retrievedRankings =
-                retrievedRankingUpdate.getRankingMap();
-        assertNotNull(retrievedRankings);
-        // The rankingUpdate file descriptor is only non-null in the new path.
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
-            assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
-        }
-        NotificationListenerService.Ranking retrievedRanking =
-                new NotificationListenerService.Ranking();
-        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
-        assertEquals(123, retrievedRanking.getRank());
-        assertTrue(retrievedRankingUpdate.equals(rankingUpdate));
-        parceledRankingUpdate.recycle();
+        detailedAssertEquals(nru, constructedNru);
     }
 
     @Test
     public void testRankingUpdate_emptyParcelInCheck() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
-
+        NotificationRankingUpdate rankingUpdate = generateUpdate(getContext());
         Parcel parceledRankingUpdate = Parcel.obtain();
         rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
 
@@ -163,37 +578,119 @@
         NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
                 parceledRankingUpdate);
         assertNull(retrievedRankingUpdate.getRankingMap());
+        parceledRankingUpdate.recycle();
     }
 
     @Test
     public void testRankingUpdate_describeContents() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
+        NotificationRankingUpdate rankingUpdate = generateUpdate(getContext());
         assertEquals(0, rankingUpdate.describeContents());
     }
 
     @Test
     public void testRankingUpdate_equals() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationListenerService.Ranking ranking = createEmptyTestRanking(TEST_KEY, 123, null);
         NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
                 new NotificationListenerService.Ranking[]{ranking});
-        // Reflexive equality.
-        assertTrue(rankingUpdate.equals(rankingUpdate));
-        // Null or wrong class inequality.
+        // Reflexive equality, including handling nulls properly
+        detailedAssertEquals(rankingUpdate, rankingUpdate);
+        // Null or wrong class inequality
         assertFalse(rankingUpdate.equals(null));
         assertFalse(rankingUpdate.equals(ranking));
 
-        // Different ranking contents inequality.
-        NotificationListenerService.Ranking ranking2 = createTestRanking(TEST_KEY, 456);
+        // Different rank inequality
+        NotificationListenerService.Ranking ranking2 = createEmptyTestRanking(TEST_KEY, 456, null);
         NotificationRankingUpdate rankingUpdate2 = new NotificationRankingUpdate(
                 new NotificationListenerService.Ranking[]{ranking2});
         assertFalse(rankingUpdate.equals(rankingUpdate2));
 
-        // Same ranking contents equality.
-        ranking2 = createTestRanking(TEST_KEY, 123);
+        // Different key inequality
+        ranking2 = createEmptyTestRanking(TEST_KEY + "DIFFERENT", 123, null);
         rankingUpdate2 = new NotificationRankingUpdate(
                 new NotificationListenerService.Ranking[]{ranking2});
-        assertTrue(rankingUpdate.equals(rankingUpdate2));
+        assertFalse(rankingUpdate.equals(rankingUpdate2));
+    }
+
+    @Test
+    public void testRankingUpdate_writesSmartActionToParcel() {
+        if (!Flags.rankingUpdateAshmem()) {
+            return;
+        }
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        PendingIntent intent = PendingIntent.getBroadcast(
+                getContext(),
+                0 /*requestCode*/,
+                new Intent("ACTION_" + TEST_KEY),
+                PendingIntent.FLAG_IMMUTABLE /*flags*/);
+        actions.add(new Notification.Action.Builder(null /*icon*/, TEST_KEY, intent).build());
+
+        NotificationListenerService.Ranking ranking =
+                createEmptyTestRanking(TEST_KEY, 123, actions);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parcel = Parcel.obtain();
+        rankingUpdate.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        SharedMemory fd = parcel.readParcelable(getClass().getClassLoader(), SharedMemory.class);
+        Bundle smartActionsBundle = parcel.readBundle(getClass().getClassLoader());
+
+        // Assert the file descriptor is valid
+        assertNotNull(fd);
+        assertFalse(fd.getFd() == -1);
+
+        // Assert that the smart action is in the parcel
+        assertNotNull(smartActionsBundle);
+        ArrayList<Notification.Action> recoveredActions =
+                smartActionsBundle.getParcelableArrayList(TEST_KEY, Notification.Action.class);
+        assertNotNull(recoveredActions);
+        assertEquals(actions.size(), recoveredActions.size());
+        assertEquals(actions.get(0).title.toString(), recoveredActions.get(0).title.toString());
+        parcel.recycle();
+    }
+
+    @Test
+    public void testRankingUpdate_handlesEmptySmartActionList() {
+        if (!Flags.rankingUpdateAshmem()) {
+            return;
+        }
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        NotificationListenerService.Ranking ranking =
+                createEmptyTestRanking(TEST_KEY, 123, actions);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parcel = Parcel.obtain();
+        rankingUpdate.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        // Ensure that despite an empty actions list, we can still unparcel the update.
+        NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel);
+        assertNotNull(newRankingUpdate);
+        assertNotNull(newRankingUpdate.getRankingMap());
+        detailedAssertEquals(rankingUpdate, newRankingUpdate);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testRankingUpdate_handlesNullSmartActionList() {
+        if (!Flags.rankingUpdateAshmem()) {
+            return;
+        }
+        NotificationListenerService.Ranking ranking =
+                createEmptyTestRanking(TEST_KEY, 123, null);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parcel = Parcel.obtain();
+        rankingUpdate.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        // Ensure that despite an empty actions list, we can still unparcel the update.
+        NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel);
+        assertNotNull(newRankingUpdate);
+        assertNotNull(newRankingUpdate.getRankingMap());
+        detailedAssertEquals(rankingUpdate, newRankingUpdate);
+        parcel.recycle();
     }
 }
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca9994..23668a4 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@
 per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
 
 # Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
new file mode 100644
index 0000000..6acab36
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class ScrollFeedbackProviderTest {
+    private final Context mContext = InstrumentationRegistry.getContext();
+
+    @Test
+    public void testDefaultProviderType() {
+        View view = new View(mContext);
+
+        ScrollFeedbackProvider provider = ScrollFeedbackProvider.createProvider(view);
+
+        assertThat(provider).isInstanceOf(HapticScrollFeedbackProvider.class);
+    }
+
+    @Test
+    public void testDefaultProvider_createsDistinctProvidesOnMultipleCalls() {
+        View view1 = new View(mContext);
+        View view2 = new View(mContext);
+
+        ScrollFeedbackProvider view1Provider1 = ScrollFeedbackProvider.createProvider(view1);
+        ScrollFeedbackProvider view1Provider2 = ScrollFeedbackProvider.createProvider(view1);
+        ScrollFeedbackProvider view2Provider = ScrollFeedbackProvider.createProvider(view2);
+
+        assertThat(view1Provider1 == view1Provider2).isFalse();
+        assertThat(view1Provider1 == view2Provider).isFalse();
+    }
+}
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051..71bdce4 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.eq;
@@ -148,6 +149,28 @@
         reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8);
     }
 
+    @Test
+    public void testCallStackDebugging_matchesFilters() {
+        SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
+
+        // Specific name, any call
+        registry.setCallStackDebuggingParams("com.android.app1", "");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+
+        // Any name, specific call
+        registry.setCallStackDebuggingParams("", "setAlpha");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+
+        // Specific name, specific call
+        registry.setCallStackDebuggingParams("com.android.app1", "setAlpha");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+    }
+
     private SurfaceControl buildTestSurface() {
         return new SurfaceControl.Builder()
                 .setContainerLayer()
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 101f7c2..5c411d5 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -31,6 +31,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Collections;
+
 /**
  * Unit test for {@link ContentCaptureManager}.
  *
@@ -69,7 +71,11 @@
         ContentCaptureOptions options =
                 createOptions(
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ false, BUFFER_SIZE));
+                                /* enableReceiver= */ false,
+                                BUFFER_SIZE,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
 
         ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
@@ -82,7 +88,11 @@
         ContentCaptureOptions options =
                 createOptions(
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ true, /* bufferSize= */ 0));
+                                /* enableReceiver= */ true,
+                                /* bufferSize= */ 0,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
 
         ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
@@ -95,7 +105,11 @@
         ContentCaptureOptions options =
                 createOptions(
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ true, BUFFER_SIZE));
+                                /* enableReceiver= */ true,
+                                BUFFER_SIZE,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
 
         ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index 3373b8b..d47d789 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -47,6 +47,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -112,7 +113,31 @@
                 createOptions(
                         /* enableContentCaptureReceiver= */ true,
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ true, -BUFFER_SIZE));
+                                /* enableReceiver= */ true,
+                                -BUFFER_SIZE,
+                                /* requiredGroups= */ List.of(List.of("a")),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
+        MainContentCaptureSession session = createSession(options);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+    }
+
+    @Test
+    public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true,
+                                BUFFER_SIZE,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
         MainContentCaptureSession session = createSession(options);
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
@@ -313,7 +338,11 @@
         return createOptions(
                 enableContentCaptureReceiver,
                 new ContentCaptureOptions.ContentProtectionOptions(
-                        enableContentProtectionReceiver, BUFFER_SIZE));
+                        enableContentProtectionReceiver,
+                        BUFFER_SIZE,
+                        /* requiredGroups= */ List.of(List.of("a")),
+                        /* optionalGroups= */ Collections.emptyList(),
+                        /* optionalGroupsThreshold= */ 0));
     }
 
     private ContentCaptureManager createManager(ContentCaptureOptions options) {
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index 39a2e0e..ba0dbf4 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -29,12 +29,13 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Handler;
-import android.os.Looper;
-import android.text.InputType;
+import android.os.test.TestLooper;
 import android.view.View;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.IContentCaptureManager;
@@ -57,6 +58,7 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.mockito.verification.VerificationMode;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -75,13 +77,25 @@
 
     private static final String PACKAGE_NAME = "com.test.package.name";
 
-    private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+    private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT";
 
-    private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+    private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT";
 
-    private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+    private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT";
 
-    private static final String SAFE_TEXT = "SAFE TEXT";
+    private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT";
+
+    private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT";
+
+    private static final String TEXT_SHARED = "TEXT SHARED TEXT";
+
+    private static final String TEXT_SAFE = "TEXT SAFE TEXT";
+
+    private static final List<List<String>> REQUIRED_GROUPS =
+            List.of(List.of("required1", "missing"), List.of("required2", "shared"));
+
+    private static final List<List<String>> OPTIONAL_GROUPS =
+            List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared"));
 
     private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
 
@@ -91,7 +105,17 @@
     private static final Set<Integer> EVENT_TYPES_TO_STORE =
             ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
 
-    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+    private static final int BUFFER_SIZE = 150;
+
+    private static final int OPTIONAL_GROUPS_THRESHOLD = 1;
+
+    private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS =
+            new ContentCaptureOptions.ContentProtectionOptions(
+                    /* enableReceiver= */ true,
+                    BUFFER_SIZE,
+                    REQUIRED_GROUPS,
+                    OPTIONAL_GROUPS,
+                    OPTIONAL_GROUPS_THRESHOLD);
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -101,16 +125,19 @@
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
 
-    private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+    private final TestLooper mTestLooper = new TestLooper();
+
+    @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor;
 
     @Before
     public void setup() {
         mContentProtectionEventProcessor =
                 new ContentProtectionEventProcessor(
                         mMockEventBuffer,
-                        new Handler(Looper.getMainLooper()),
+                        new Handler(mTestLooper.getLooper()),
                         mMockContentCaptureManager,
-                        PACKAGE_NAME);
+                        PACKAGE_NAME,
+                        OPTIONS);
     }
 
     @Test
@@ -156,347 +183,224 @@
     }
 
     @Test
-    public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+    public void processEvent_loginDetected_true_eventText() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ TEXT_REQUIRED1,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ TEXT_REQUIRED2,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ TEXT_OPTIONAL1,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ null));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_viewNodeText() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ TEXT_REQUIRED1,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ TEXT_REQUIRED2,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ TEXT_OPTIONAL1,
+                        /* hintText= */ null));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_hintText() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ TEXT_OPTIONAL1));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_usesContains() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_missingRequiredGroups() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_missingOptionalGroups() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_safeText() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_sharedTextOnce() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
 
         for (int type = -100; type <= 100; type++) {
             if (type == TYPE_VIEW_APPEARED) {
                 continue;
             }
-
-            mContentProtectionEventProcessor.processEvent(createEvent(type));
-
-            assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-            assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+            ContentCaptureEvent event = createEvent(type);
+            event.setText(TEXT_OPTIONAL1);
+            mContentProtectionEventProcessor.processEvent(event);
         }
 
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
+        assertLoginNotDetected();
     }
 
     @Test
-    public void processEvent_loginDetected() throws Exception {
+    public void processEvent_loginDetected_true_belowResetLimit() throws Exception {
         when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
 
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected();
-    }
-
-    @Test
-    public void processEvent_loginDetected_passwordFieldNotDetected() {
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void processEvent_loginDetected_suspiciousTextNotDetected() {
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void processEvent_loginDetected_withoutViewNode() {
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void processEvent_loginDetected_belowResetLimit() throws Exception {
-        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+        for (int i = 0; i < BUFFER_SIZE - 2; i++) {
             mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
         }
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
+        assertLoginNotDetected();
 
-        mContentProtectionEventProcessor.processEvent(event);
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected();
+        assertLoginDetected();
     }
 
     @Test
-    public void processEvent_loginDetected_aboveResetLimit() throws Exception {
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+    public void processEvent_loginDetected_false_aboveResetLimit() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
 
-        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+        for (int i = 0; i < BUFFER_SIZE - 1; i++) {
             mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
         }
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
+        assertLoginNotDetected();
 
-        mContentProtectionEventProcessor.processEvent(event);
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
+        assertLoginNotDetected();
     }
 
     @Test
     public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
         when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        for (int i = 0; i < 2; i++) {
+            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        }
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected();
+        assertLoginDetected();
     }
 
     @Test
     public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
         when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
         mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
 
         mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
         mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, times(2)).clear();
-        verify(mMockEventBuffer, times(2)).toArray();
-        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
-    }
-
-    @Test
-    public void isPasswordField_android() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_android_withoutClassName() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_android_wrongClassName() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        "wrong.prefix" + ANDROID_CLASS_NAME,
-                        InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_android_wrongInputType() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_webView() throws Exception {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(
-                        /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
-        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected(event, /* times= */ 1);
-    }
-
-    @Test
-    public void isPasswordField_webView_withClassName() {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(
-                        /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_webView_withSafeViewNodeText() {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(
-                        /* className= */ null, /* eventText= */ null, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_webView_withEventText() {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_withSafeText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_eventText_suspiciousText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_viewNodeText_suspiciousText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_eventText_passwordText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_viewNodeText_passwordText() {
-        // Specify the class to differ from {@link isPasswordField_webView} test in this version
-        ContentCaptureEvent event =
-                createProcessEvent(
-                        "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
+        assertLoginDetected(times(2));
     }
 
     private static ContentCaptureEvent createEvent(int type) {
@@ -511,20 +415,20 @@
         return createEvent(TYPE_VIEW_APPEARED);
     }
 
+    private ContentCaptureEvent createProcessEvent(@Nullable String eventText) {
+        return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null);
+    }
+
     private ContentCaptureEvent createProcessEvent(
-            @Nullable String className,
-            int inputType,
-            @Nullable String eventText,
-            @Nullable String viewNodeText) {
+            @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) {
         View view = new View(mContext);
         ViewStructureImpl viewStructure = new ViewStructureImpl(view);
-        if (className != null) {
-            viewStructure.setClassName(className);
-        }
         if (viewNodeText != null) {
             viewStructure.setText(viewNodeText);
         }
-        viewStructure.setInputType(inputType);
+        if (hintText != null) {
+            viewStructure.setHint(hintText);
+        }
 
         ContentCaptureEvent event = createProcessEvent();
         event.setViewNode(viewStructure.getNode());
@@ -535,34 +439,28 @@
         return event;
     }
 
-    private ContentCaptureEvent createAndroidPasswordFieldEvent(
-            @Nullable String className, int inputType) {
-        return createProcessEvent(
-                className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+    private void assertLoginNotDetected() {
+        mTestLooper.dispatchAll();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
     }
 
-    private ContentCaptureEvent createWebViewPasswordFieldEvent(
-            @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
-        return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+    private void assertLoginDetected() throws Exception {
+        assertLoginDetected(times(1));
     }
 
-    private ContentCaptureEvent createSuspiciousTextEvent(
-            @Nullable String eventText, @Nullable String viewNodeText) {
-        return createProcessEvent(
-                /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
-    }
+    private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception {
+        mTestLooper.dispatchAll();
+        verify(mMockEventBuffer, verificationMode).clear();
+        verify(mMockEventBuffer, verificationMode).toArray();
 
-    private void assertOnLoginDetected() throws Exception {
-        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
-    }
-
-    private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
         ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
-        verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+        verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture());
 
         assertThat(captor.getValue()).isNotNull();
         List<ContentCaptureEvent> actual = captor.getValue().getList();
         assertThat(actual).isNotNull();
-        assertThat(actual).containsExactly(event);
+        assertThat(actual).containsExactly(PROCESS_EVENT);
     }
 }
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
index 1459799..fbe478e 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -44,68 +44,74 @@
 
     private static final String TEXT = "TEST_TEXT";
 
-    private static final ContentCaptureEvent EVENT = createEvent();
-
-    private static final ViewNode VIEW_NODE = new ViewNode();
-
-    private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+    private static final String TEXT_LOWER = TEXT.toLowerCase();
 
     @Test
-    public void event_getEventText_null() {
-        String actual = ContentProtectionUtils.getEventText(EVENT);
+    public void getEventTextLower_null() {
+        String actual = ContentProtectionUtils.getEventTextLower(createEvent());
 
         assertThat(actual).isNull();
     }
 
     @Test
-    public void event_getEventText_notNull() {
-        ContentCaptureEvent event = createEvent();
-        event.setText(TEXT);
+    public void getEventTextLower_notNull() {
+        String actual = ContentProtectionUtils.getEventTextLower(createEventWithText());
 
-        String actual = ContentProtectionUtils.getEventText(event);
-
-        assertThat(actual).isEqualTo(TEXT);
+        assertThat(actual).isEqualTo(TEXT_LOWER);
     }
 
     @Test
-    public void event_getViewNodeText_null() {
-        String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+    public void getViewNodeTextLower_null() {
+        String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode());
 
         assertThat(actual).isNull();
     }
 
     @Test
-    public void event_getViewNodeText_notNull() {
-        ContentCaptureEvent event = createEvent();
-        event.setViewNode(VIEW_NODE_WITH_TEXT);
+    public void getViewNodeTextLower_notNull() {
+        String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText());
 
-        String actual = ContentProtectionUtils.getViewNodeText(event);
-
-        assertThat(actual).isEqualTo(TEXT);
+        assertThat(actual).isEqualTo(TEXT_LOWER);
     }
 
     @Test
-    public void viewNode_getViewNodeText_null() {
-        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+    public void getHintTextLower_null() {
+        String actual = ContentProtectionUtils.getHintTextLower(new ViewNode());
 
         assertThat(actual).isNull();
     }
 
     @Test
-    public void viewNode_getViewNodeText_notNull() {
-        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+    public void getHintTextLower_notNull() {
+        String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint());
 
-        assertThat(actual).isEqualTo(TEXT);
+        assertThat(actual).isEqualTo(TEXT_LOWER);
     }
 
     private static ContentCaptureEvent createEvent() {
         return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
     }
 
-    private static ViewNode createViewNodeWithText() {
+    private static ContentCaptureEvent createEventWithText() {
+        ContentCaptureEvent event = createEvent();
+        event.setText(TEXT);
+        return event;
+    }
+
+    private static ViewStructureImpl createViewStructureImpl() {
         View view = new View(ApplicationProvider.getApplicationContext());
-        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
-        viewStructure.setText(TEXT);
-        return viewStructure.getNode();
+        return new ViewStructureImpl(view);
+    }
+
+    private static ViewNode createViewNodeWithText() {
+        ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+        viewStructureImpl.setText(TEXT);
+        return viewStructureImpl.getNode();
+    }
+
+    private static ViewNode createViewNodeWithHint() {
+        ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+        viewStructureImpl.setHint(TEXT);
+        return viewStructureImpl.getNode();
     }
 }
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 86f26e5..df212eb 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -17,7 +17,9 @@
 package android.widget;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.platform.test.annotations.Presubmit;
 import android.util.PollingCheck;
 
@@ -32,6 +34,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 @Presubmit
@@ -49,23 +54,43 @@
     }
 
     @Test
-    public void testScrollAfterFlingTop() {
-        mHorizontalScrollView.scrollTo(100, 0);
-        mHorizontalScrollView.fling(-10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+    public void testScrollAfterFlingLeft() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowLeft = edgeEffect;
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame
+        PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);
         assertEquals(0, mHorizontalScrollView.getScrollX());
     }
 
     @Test
-    public void testScrollAfterFlingBottom() {
+    public void testScrollAfterFlingRight() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowRight = edgeEffect;
         int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
         int maxScroll = childWidth - mHorizontalScrollView.getWidth();
-        mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
-        mHorizontalScrollView.fling(10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame
         PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
         assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
     }
+
+    static class WatchedEdgeEffect extends EdgeEffect {
+        public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
+
+        WatchedEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            super.onAbsorb(velocity);
+            onAbsorbLatch.countDown();
+        }
+    }
 }
 
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 263e563..6229530 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -154,7 +155,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -171,7 +173,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -241,7 +244,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -261,7 +265,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -281,7 +286,8 @@
                     eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
             verify(mTransaction).setFrameRateCategory(
                     eq(mDefaultDisplayRoot),
-                    eq(FRAME_RATE_CATEGORY_DEFAULT));
+                    eq(FRAME_RATE_CATEGORY_DEFAULT),
+                    eq(false));
             verify(mTransaction).setEarlyWakeupEnd();
             verify(mTransaction).applyAsyncUnsafe();
             verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -299,7 +305,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -310,7 +317,7 @@
                 mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);
         // Verify we never call SF and perf manager since session1 is already running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -318,7 +325,7 @@
         session2.close();
         // Verify we have not cleaned up because session1 is still running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -330,7 +337,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -348,7 +356,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -363,7 +372,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -378,13 +388,15 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction, never()).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 anyInt());
         verify(mTransaction, never()).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                anyInt());
+                anyInt(),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -401,7 +413,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 681ba9c..06b62f8 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -29,6 +29,8 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.endsWith;
 import static org.mockito.ArgumentMatchers.any;
@@ -39,6 +41,8 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -47,11 +51,17 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
+import android.view.View;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 
 import androidx.lifecycle.Lifecycle;
@@ -90,6 +100,9 @@
     private TestAccessibilityShortcutChooserActivity mActivity;
 
     @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
     private AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -101,6 +114,8 @@
     private ApplicationInfo mApplicationInfo;
     @Mock
     private IAccessibilityManager mAccessibilityManagerService;
+    @Mock
+    private KeyguardManager mKeyguardManager;
 
     @Before
     public void setUp() throws Exception {
@@ -116,22 +131,19 @@
                         Collections.singletonList(mAccessibilityServiceInfo)));
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 anyString(), anyInt(), anyInt())).thenReturn(true);
-        TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
-        mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-        mScenario.onActivity(activity -> mActivity = activity);
-        mScenario.moveToState(Lifecycle.State.CREATED);
-        mScenario.moveToState(Lifecycle.State.STARTED);
-        mScenario.moveToState(Lifecycle.State.RESUMED);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+        TestAccessibilityShortcutChooserActivity.setupForTesting(
+                mAccessibilityManagerService, mKeyguardManager);
     }
 
     @After
     public void cleanUp() {
-        mScenario.moveToState(Lifecycle.State.DESTROYED);
+        mScenario.close();
     }
 
     @Test
     public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
+        launchActivity();
         openShortcutsList();
 
         // Performing the double-click is flaky so retry if needed.
@@ -154,6 +166,7 @@
             throws Exception {
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
+        launchActivity();
         openShortcutsList();
 
         onView(withText(TEST_LABEL)).perform(scrollTo(), click());
@@ -165,6 +178,7 @@
     @Test
     public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+        launchActivity();
         openShortcutsList();
 
         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
@@ -176,6 +190,7 @@
     @Test
     public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+        launchActivity();
         openShortcutsList();
 
         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
@@ -184,6 +199,30 @@
         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN)
+    public void createDialog_onLockscreen_hasExpectedContent() {
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+        launchActivity();
+
+        final AlertDialog dialog = mActivity.getMenuDialog();
+
+        assertThat(dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility())
+                .isEqualTo(View.GONE);
+        assertThat(dialog.getWindow().getAttributes().flags
+                & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+                .isEqualTo(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+    }
+
+    private void launchActivity() {
+        mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        mScenario.onActivity(activity -> mActivity = activity);
+        mScenario.moveToState(Lifecycle.State.CREATED);
+        mScenario.moveToState(Lifecycle.State.STARTED);
+        mScenario.moveToState(Lifecycle.State.RESUMED);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
     private void openShortcutsList() {
         UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL));
         if (editButton != null) {
@@ -198,9 +237,13 @@
     public static class TestAccessibilityShortcutChooserActivity extends
             AccessibilityShortcutChooserActivity {
         private static IAccessibilityManager sAccessibilityManagerService;
+        private static KeyguardManager sKeyguardManager;
 
-        public static void setupForTesting(IAccessibilityManager accessibilityManagerService) {
+        public static void setupForTesting(
+                IAccessibilityManager accessibilityManagerService,
+                KeyguardManager keyguardManager) {
             sAccessibilityManagerService = accessibilityManagerService;
+            sKeyguardManager = keyguardManager;
         }
 
         @Override
@@ -210,6 +253,9 @@
                 return new AccessibilityManager(this, new Handler(getMainLooper()),
                         sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
             }
+            if (Context.KEYGUARD_SERVICE.equals(name)) {
+                return sKeyguardManager;
+            }
 
             return super.getSystemService(name);
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 2cbeaa1..f846ac5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -34,6 +34,7 @@
         KernelSingleUidTimeReaderTest.class,
         LongArrayMultiStateCounterTest.class,
         LongMultiStateCounterTest.class,
+        MonotonicClockTest.class,
         PowerProfileTest.class,
         PowerStatsTest.class,
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
new file mode 100644
index 0000000..7951270
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MonotonicClockTest {
+    private final MockClock mClock = new MockClock();
+
+    @Test
+    public void persistence() throws IOException {
+        MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+        mClock.realtime = 234;
+
+        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        monotonicClock.writeXml(out, Xml.newFastSerializer());
+        String xml = out.toString();
+        assertThat(xml).contains("timeshift=\"1234\"");
+
+        mClock.realtime = 42;
+        MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
+        newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()),
+                Xml.newFastPullParser());
+
+        mClock.realtime = 2000;
+        assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 8fa6376..77202d1 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -21,16 +21,12 @@
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.annotation.XmlRes;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -540,66 +536,4 @@
     private void assertEquals(double expected, double actual) {
         Assert.assertEquals(expected, actual, 0.1);
     }
-
-    @Test
-    public void powerBrackets_specifiedInPowerProfile() {
-        mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets);
-        mProfile.initCpuPowerBrackets(8);
-
-        int cpuPowerBracketCount = mProfile.getCpuPowerBracketCount();
-        assertThat(cpuPowerBracketCount).isEqualTo(2);
-        assertThat(new int[]{
-                mProfile.getCpuPowerBracketForScalingStep(0, 0),
-                mProfile.getCpuPowerBracketForScalingStep(4, 0),
-                mProfile.getCpuPowerBracketForScalingStep(4, 1),
-        }).isEqualTo(new int[]{1, 1, 0});
-    }
-
-    @Test
-    public void powerBrackets_automatic() {
-        mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
-        CpuScalingPolicies scalingPolicies = new CpuScalingPolicies(
-                new SparseArray<>() {{
-                    put(0, new int[]{0, 1, 2});
-                    put(3, new int[]{3, 4});
-                }},
-                new SparseArray<>() {{
-                    put(0, new int[]{300000, 1000000, 2000000});
-                    put(3, new int[]{300000, 1000000, 2500000, 3000000});
-                }});
-
-        assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(3);
-        assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 0))
-                .isEqualTo("0/300(10.0)");
-        assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 1))
-                .isEqualTo("0/1000(20.0), 0/2000(30.0), 3/300(25.0)");
-        assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 2))
-                .isEqualTo("3/1000(35.0), 3/2500(50.0), 3/3000(60.0)");
-        assertThat(new int[]{
-                mProfile.getCpuPowerBracketForScalingStep(0, 0),
-                mProfile.getCpuPowerBracketForScalingStep(0, 1),
-                mProfile.getCpuPowerBracketForScalingStep(0, 2),
-                mProfile.getCpuPowerBracketForScalingStep(3, 0),
-                mProfile.getCpuPowerBracketForScalingStep(3, 1),
-                mProfile.getCpuPowerBracketForScalingStep(3, 2),
-                mProfile.getCpuPowerBracketForScalingStep(3, 3),
-        }).isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2});
-    }
-
-    @Test
-    public void powerBrackets_moreBracketsThanStates() {
-        mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
-        mProfile.initCpuPowerBrackets(8);
-
-        assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(7);
-        assertThat(new int[]{
-                mProfile.getCpuPowerBracketForScalingStep(0, 0),
-                mProfile.getCpuPowerBracketForScalingStep(0, 1),
-                mProfile.getCpuPowerBracketForScalingStep(0, 2),
-                mProfile.getCpuPowerBracketForScalingStep(3, 0),
-                mProfile.getCpuPowerBracketForScalingStep(3, 1),
-                mProfile.getCpuPowerBracketForScalingStep(3, 2),
-                mProfile.getCpuPowerBracketForScalingStep(3, 3),
-        }).isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6});
-    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9c65287..35498b7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1801,6 +1801,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-504637678": {
+      "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+    },
     "-503656156": {
       "message": "Update process config of %s to new config %s",
       "level": "VERBOSE",
@@ -2359,6 +2365,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "33989965": {
+      "message": " Met condition %s for #%d (%d left)",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "34106798": {
       "message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?",
       "level": "VERBOSE",
@@ -2881,6 +2893,12 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "419378610": {
+      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "422634333": {
       "message": "First draw done in potential wallpaper target %s",
       "level": "VERBOSE",
@@ -3739,6 +3757,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityClientController.java"
     },
+    "1309365288": {
+      "message": "Removing dim surface %s on transaction %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+    },
     "1316533291": {
       "message": "State movement: %s from:%s to:%s reason:%s",
       "level": "VERBOSE",
@@ -4003,6 +4027,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1620751818": {
+      "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+    },
     "1621562070": {
       "message": "    startWCT=%s",
       "level": "VERBOSE",
@@ -4321,12 +4351,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "1936800105": {
-      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "1945495497": {
       "message": "Focused window didn't have a valid surface drawn.",
       "level": "DEBUG",
@@ -4465,6 +4489,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "2053743391": {
+      "message": " Add condition %s for #%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "2060978050": {
       "message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
       "level": "WARN",
@@ -4525,6 +4555,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "2124732293": {
+      "message": "#%d: Met condition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "2128917433": {
       "message": "onProposedRotationChanged, rotation=%d",
       "level": "VERBOSE",
@@ -4560,6 +4596,9 @@
     "WM_DEBUG_CONTENT_RECORDING": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_DIMMER": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_DRAW": {
       "tag": "WindowManager"
     },
diff --git a/framework-minus-apex-ravenwood-policies.txt b/framework-minus-apex-ravenwood-policies.txt
new file mode 100644
index 0000000..6bac58b
--- /dev/null
+++ b/framework-minus-apex-ravenwood-policies.txt
@@ -0,0 +1 @@
+# Ravenwood "policy" file for framework-minus-apex.
diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp
index 63d1f6d..db37a387 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -7,6 +7,12 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+aconfig_declarations {
+    name: "framework_graphics_flags",
+    package: "com.android.graphics.flags",
+    srcs: ["android/framework_graphics.aconfig"],
+}
+
 filegroup {
     name: "framework-graphics-nonupdatable-sources",
     srcs: [
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
new file mode 100644
index 0000000..9a0a22a
--- /dev/null
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.graphics.flags"
+
+flag {
+     name: "exact_compute_bounds"
+     namespace: "core_graphics"
+     description: "Add a function without unused exact param for computeBounds."
+     bug: "304478551"
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index b5fb13d..0a6fb84 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,11 +16,14 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.graphics.hwui.flags.Flags;
+
 import libcore.util.NativeAllocationRegistry;
 
 /**
@@ -125,6 +128,7 @@
      * Creates a new gainmap using the provided gainmap as the metadata source and the provided
      * bitmap as the replacement for the gainmapContents
      */
+    @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
     public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
         this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
     }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 9d32272..92c4de6 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -16,14 +16,20 @@
 
 package android.graphics;
 
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.annotation.Size;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
@@ -611,6 +617,7 @@
         mCompatScaling = mInvCompatScaling = 1;
         setTextLocales(LocaleList.getAdjustedDefault());
         mColor = Color.pack(Color.BLACK);
+        resetElegantTextHeight();
     }
 
     /**
@@ -651,7 +658,7 @@
 
         mBidiFlags = BIDI_DEFAULT_LTR;
         setTextLocales(LocaleList.getAdjustedDefault());
-        setElegantTextHeight(false);
+        resetElegantTextHeight();
         mFontFeatureSettings = null;
         mFontVariationSettings = null;
 
@@ -1606,7 +1613,7 @@
     /**
      * Returns the color of the shadow layer.
      *
-     * @return the shadow layer's color encoded as a {@link ColorLong}.
+     * @return the shadow layer's color encoded as a {@code ColorLong}.
      * @see #setShadowLayer(float,float,float,int)
      * @see #setShadowLayer(float,float,float,long)
      * @see Color
@@ -1732,12 +1739,30 @@
     /**
      * Get the elegant metrics flag.
      *
+     * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
+     * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
+     * later.
+     *
      * @return true if elegant metrics are enabled for text drawing.
      */
     public boolean isElegantTextHeight() {
-        return nIsElegantTextHeight(mNativePaint);
+        int rawValue = nGetElegantTextHeight(mNativePaint);
+        switch (rawValue) {
+            case ELEGANT_TEXT_HEIGHT_DISABLED:
+                return false;
+            case ELEGANT_TEXT_HEIGHT_ENABLED:
+                return true;
+            case ELEGANT_TEXT_HEIGHT_UNSET:
+            default:
+                return com.android.text.flags.Flags.deprecateUiFonts();
+        }
     }
 
+    // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp
+    private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1;
+    private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+    private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
     /**
      * Set the paint's elegant height metrics flag. This setting selects font
      * variants that have not been compacted to fit Latin-based vertical
@@ -1746,7 +1771,29 @@
      * @param elegant set the paint's elegant metrics flag for drawing text.
      */
     public void setElegantTextHeight(boolean elegant) {
-        nSetElegantTextHeight(mNativePaint, elegant);
+        nSetElegantTextHeight(mNativePaint,
+                elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
+    }
+
+    /**
+     * A change ID for deprecating UI fonts.
+     *
+     * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
+     * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
+     * later.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long DEPRECATE_UI_FONT = 279646685L;
+
+    private void resetElegantTextHeight() {
+        if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) {
+            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET);
+        } else {
+            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED);
+        }
     }
 
     /**
@@ -2110,6 +2157,31 @@
          * The recommended additional space to add between lines of text.
          */
         public float   leading;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || !(o instanceof FontMetrics)) return false;
+            FontMetrics that = (FontMetrics) o;
+            return that.top == top && that.ascent == ascent && that.descent == descent
+                    && that.bottom == bottom && that.leading == leading;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(top, ascent, descent, bottom, leading);
+        }
+
+        @Override
+        public String toString() {
+            return "FontMetrics{"
+                    + "top=" + top
+                    + ", ascent=" + ascent
+                    + ", descent=" + descent
+                    + ", bottom=" + bottom
+                    + ", leading=" + leading
+                    + '}';
+        }
     }
 
     /**
@@ -2125,7 +2197,7 @@
      * @return the font's recommended interline spacing.
      */
     public float getFontMetrics(FontMetrics metrics) {
-        return nGetFontMetrics(mNativePaint, metrics);
+        return nGetFontMetrics(mNativePaint, metrics, false);
     }
 
     /**
@@ -2139,6 +2211,32 @@
     }
 
     /**
+     * Get the font metrics used for the locale
+     *
+     * Obtain the metrics of the font that is used for the specified locale by
+     * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent
+     * and maximum descent will be set.
+     *
+     * This API is useful for determining the default line height of the empty text field, e.g.
+     * {@link android.widget.EditText}.
+     *
+     * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its
+     * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the
+     * descent will be the custom font's descent or larger.
+     *
+     * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g.
+     * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the
+     * ascent will be the serif font's ascent or smaller, the descent will be the serif font's
+     * descent or larger.
+     *
+     * @param metrics an output parameter. All members will be set by calling this function.
+     */
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public void getFontMetricsForLocale(@NonNull FontMetrics metrics) {
+        nGetFontMetrics(mNativePaint, metrics, true);
+    }
+
+    /**
      * Returns the font metrics value for the given text.
      *
      * If the text is rendered with multiple font files, this function returns the large ascent and
@@ -2280,6 +2378,33 @@
          */
         public int   leading;
 
+        /**
+         * Set values from {@link FontMetricsInt}.
+         * @param fontMetricsInt a font metrics.
+         */
+        @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+        public void set(@NonNull FontMetricsInt fontMetricsInt) {
+            top = fontMetricsInt.top;
+            ascent = fontMetricsInt.ascent;
+            descent = fontMetricsInt.descent;
+            bottom = fontMetricsInt.bottom;
+            leading = fontMetricsInt.leading;
+        }
+
+        /**
+         * Set values from {@link FontMetrics} with rounding accordingly.
+         * @param fontMetrics a font metrics.
+         */
+        @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+        public void set(@NonNull FontMetrics fontMetrics) {
+            // See GraphicsJNI::set_metrics_int method for consistency.
+            top = (int) Math.floor(fontMetrics.top);
+            ascent = Math.round(fontMetrics.ascent);
+            descent = Math.round(fontMetrics.descent);
+            bottom = (int) Math.ceil(fontMetrics.bottom);
+            leading = Math.round(fontMetrics.leading);
+        }
+
         @Override public String toString() {
             return "FontMetricsInt: top=" + top + " ascent=" + ascent +
                     " descent=" + descent + " bottom=" + bottom +
@@ -2318,7 +2443,7 @@
      * @return the font's interline spacing.
      */
     public int getFontMetricsInt(FontMetricsInt fmi) {
-        return nGetFontMetricsInt(mNativePaint, fmi);
+        return nGetFontMetricsInt(mNativePaint, fmi, false);
     }
 
     public FontMetricsInt getFontMetricsInt() {
@@ -2328,6 +2453,32 @@
     }
 
     /**
+     * Get the font metrics used for the locale
+     *
+     * Obtain the metrics of the font that is used for the specified locale by
+     * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent
+     * and maximum descent will be set.
+     *
+     * This API is useful for determining the default line height of the empty text field, e.g.
+     * {@link android.widget.EditText}.
+     *
+     * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its
+     * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the
+     * descent will be the custom font's descent or larger.
+     *
+     * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g.
+     * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the
+     * ascent will be the serif font's ascent or smaller, the descent will be the serif font's
+     * descent or larger.
+     *
+     * @param metrics an output parameter. All members will be set by calling this function.
+     */
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) {
+        nGetFontMetricsInt(mNativePaint, metrics, true);
+    }
+
+    /**
      * Return the recommend line spacing based on the current typeface and
      * text size.
      *
@@ -3446,9 +3597,11 @@
     @FastNative
     private static native void nSetFontFeatureSettings(long paintPtr, String settings);
     @FastNative
-    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics);
+    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics,
+            boolean useLocale);
     @FastNative
-    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
+    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi,
+            boolean useLocale);
 
     // ---------------- @CriticalNative ------------------------
 
@@ -3551,9 +3704,9 @@
     @CriticalNative
     private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
     @CriticalNative
-    private static native boolean nIsElegantTextHeight(long paintPtr);
+    private static native int nGetElegantTextHeight(long paintPtr);
     @CriticalNative
-    private static native void nSetElegantTextHeight(long paintPtr, boolean elegant);
+    private static native void nSetElegantTextHeight(long paintPtr, int elegant);
     @CriticalNative
     private static native float nGetTextSize(long paintPtr);
     @CriticalNative
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c9c1b23..deb89fa 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,11 +16,14 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
 
+import com.android.graphics.flags.Flags;
+
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
 
@@ -309,6 +312,7 @@
      *
      * @param bounds Returns the computed bounds of the path's control points.
      */
+    @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS)
     public void computeBounds(@NonNull RectF bounds) {
         nComputeBounds(mNativePath, bounds);
     }
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 4461f39..c2a7a84 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -109,9 +109,8 @@
             + "    float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
             + "    vec2 uv = p * in_resolutionScale;\n"
             + "    vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
-            + "    float turbulence = turbulence(uv, in_turbulencePhase);\n"
-            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha "
-            + "* turbulence;\n"
+            + "    float turb = turbulence(uv, in_turbulencePhase);\n"
+            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n"
             + "    float fade = min(fadeIn, 1. - fadeOutRipple);\n"
             + "    float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade "
             + "* in_color.a;\n"
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index 6e04a2f..ba5628c 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -182,7 +182,7 @@
 
             // For ignoring the customization, consume the new-locale-family element but don't
             // register any customizations.
-            if (com.android.text.flags.Flags.customLocaleFallback()) {
+            if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) {
                 outCustomization.add(new FontConfig.Customization.LocaleFallback(
                         Locale.forLanguageTag(lang), intOp, family));
             }
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 4c75356..685fd82 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -16,7 +16,7 @@
 
 package android.graphics.fonts;
 
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -145,7 +145,7 @@
          * @return A variable font family. null if a variable font cannot be built from the given
          *         fonts.
          */
-        @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+        @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
         public @Nullable FontFamily buildVariableFamily() {
             int variableFamilyType = analyzeAndResolveVariableType(mFonts);
             if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 618aa5b..3ef714ed 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -241,7 +241,7 @@
             int configVersion
     ) {
         final String fontsXml;
-        if (com.android.text.flags.Flags.deprecateFontsXml()) {
+        if (com.android.text.flags.Flags.newFontsFallbackXml()) {
             fontsXml = FONTS_XML;
         } else {
             fontsXml = LEGACY_FONTS_XML;
@@ -272,7 +272,7 @@
      */
     public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
         final String fontsXml;
-        if (com.android.text.flags.Flags.deprecateFontsXml()) {
+        if (com.android.text.flags.Flags.newFontsFallbackXml()) {
             fontsXml = FONTS_XML;
         } else {
             fontsXml = LEGACY_FONTS_XML;
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index f5e5803..62195856 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -17,11 +17,17 @@
 package android.graphics.text;
 
 import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.FLAG_WORD_STYLE_AUTO;
 
 import android.annotation.FlaggedApi;
 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.os.Build;
+import android.os.LocaleList;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -37,14 +43,24 @@
 public final class LineBreakConfig {
 
     /**
+     * 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.
      *
+     * <p>
      * This is a special value of hyphenation preference indicating no hyphenation preference is
      * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
      * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
      * overridden config will be kept if the hyphenation preference of overriding config is
      * {@link #HYPHENATION_UNSPECIFIED}.
      *
+     * <p>
      * <pre>
      *     val override = LineBreakConfig.Builder()
      *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
@@ -57,6 +73,7 @@
      *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
      * </pre>
      *
+     * <p>
      * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
      * layout/rendering.
      */
@@ -89,6 +106,7 @@
     /**
      * No line break style is specified.
      *
+     * <p>
      * This is a special value of line break style indicating no style value is specified.
      * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
      * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config
@@ -107,8 +125,12 @@
      *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
      * </pre>
      *
-     * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
-     * layout/rendering.
+     * <p>
+     * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if the target SDK version is API
+     * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+     * layout/rendering. This value is resolved to {@link #LINE_BREAK_STYLE_AUTO} if the target SDK
+     * version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+     * used for text layout/rendering.
      */
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1;
@@ -149,10 +171,29 @@
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_STYLE_NO_BREAK = 4;
 
+    /**
+     * A special value for the line breaking style option.
+     *
+     * <p>
+     * The auto option for the line break style set the line break style based on the locale of the
+     * text rendering context. You can specify the context locale by
+     * {@link android.widget.TextView#setTextLocales(LocaleList)} or
+     * {@link android.graphics.Paint#setTextLocales(LocaleList)}.
+     *
+     * <p>
+     * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+     * - If at least one locale in the locale list contains Japanese script, this option is
+     * equivalent to {@link #LINE_BREAK_STYLE_STRICT}.
+     * - Otherwise, this option is equivalent to {@link #LINE_BREAK_STYLE_NONE}.
+     */
+    @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+    public static final int LINE_BREAK_STYLE_AUTO = 5;
+
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
             LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
-            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK
+            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK,
+            LINE_BREAK_STYLE_AUTO
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakStyle {}
@@ -178,8 +219,11 @@
      *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
      * </pre>
      *
-     * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for
-     * text layout/rendering.
+     * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if the target SDK version is
+     * API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+     * layout/rendering. This value is resolved to {@link #LINE_BREAK_WORD_STYLE_AUTO} if the target
+     * SDK version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+     * used for text layout/rendering.
      */
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1;
@@ -199,9 +243,29 @@
      */
     public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
 
+    /**
+     * A special value for the line breaking word style option.
+     *
+     * <p>
+     * The auto option for the line break word style does some heuristics based on locales and line
+     * count.
+     *
+     * <p>
+     * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+     * - If at least one locale in the locale list contains Korean script, this option is equivalent
+     * to {@link #LINE_BREAK_WORD_STYLE_PHRASE}.
+     * - If not, then if at least one locale in the locale list contains Japanese script, this
+     * option is equivalent to {@link #LINE_BREAK_WORD_STYLE_PHRASE} if the result of its line
+     * count is less than 5 lines.
+     * - Otherwise, this option is equivalent to {@link #LINE_BREAK_WORD_STYLE_NONE}.
+     */
+    @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+    public static final int LINE_BREAK_WORD_STYLE_AUTO = 2;
+
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
-        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED
+        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED,
+            LINE_BREAK_WORD_STYLE_AUTO
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakWordStyle {}
@@ -270,6 +334,8 @@
         /**
          * Resets this builder to the given config state.
          *
+         * @param config a config value used for resetting. {@code null} is allowed. If {@code null}
+         *              is passed, all configs are reset to unspecified.
          * @return This {@code Builder}.
          * @hide
          */
@@ -418,11 +484,13 @@
      * @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;
         if (config == null) {
-            return LINE_BREAK_STYLE_NONE;
+            return defaultStyle;
         }
         return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
-                ? LINE_BREAK_STYLE_NONE : config.mLineBreakStyle;
+                ? defaultStyle : config.mLineBreakStyle;
     }
 
     /**
@@ -444,11 +512,13 @@
      */
     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;
         if (config == null) {
-            return LINE_BREAK_WORD_STYLE_NONE;
+            return defaultWordStyle;
         }
         return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
-                ? LINE_BREAK_WORD_STYLE_NONE : config.mLineBreakWordStyle;
+                ? defaultWordStyle : config.mLineBreakWordStyle;
     }
 
     /**
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index dc2e794..0e3fb16 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -249,7 +249,7 @@
          * @param useBoundsForWidth True for using bounding box, false for advances.
          * @return this builder instance
          * @see Layout#getUseBoundsForWidth()
-         * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
+         * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
          */
         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 569f9b6..7932e33 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -16,7 +16,7 @@
 
 package android.graphics.text;
 
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
@@ -173,7 +173,7 @@
      * @param index the glyph index
      * @return true if the fake bold option is on, otherwise off.
      */
-    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+    @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
     public boolean getFakeBold(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         return nGetFakeBold(mLayoutPtr, index);
@@ -185,7 +185,7 @@
      * @param index the glyph index
      * @return true if the fake italic option is on, otherwise off.
      */
-    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+    @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
     public boolean getFakeItalic(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         return nGetFakeItalic(mLayoutPtr, index);
@@ -195,7 +195,7 @@
      * A special value returned by {@link #getWeightOverride(int)} and
      * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
      */
-    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+    @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
     public static final float NO_OVERRIDE = Float.MIN_VALUE;
 
     /**
@@ -205,7 +205,7 @@
      * @param index the glyph index
      * @return overridden weight value or {@link #NO_OVERRIDE}.
      */
-    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+    @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
     public float getWeightOverride(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         float value = nGetWeightOverride(mLayoutPtr, index);
@@ -223,7 +223,7 @@
      * @param index the glyph index
      * @return overridden weight value or {@link #NO_OVERRIDE}.
      */
-    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+    @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
     public float getItalicOverride(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         float value = nGetItalicOverride(mLayoutPtr, index);
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0e198d5..e6de597 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -322,7 +322,7 @@
         }
 
         /**
-         * Returns the {@link CopyResultStatus} of the copy request.
+         * Returns the status of the copy request.
          */
         public @CopyResultStatus int getStatus() {
             return mStatus;
diff --git a/keystore/aaid/aidl/Android.bp b/keystore/aaid/aidl/Android.bp
new file mode 100644
index 0000000..97acfb4
--- /dev/null
+++ b/keystore/aaid/aidl/Android.bp
@@ -0,0 +1,31 @@
+// Copyright 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.security.aaid_aidl",
+    srcs: ["android/security/keystore/*.aidl"],
+    unstable: true,
+    backend: {
+        rust: {
+            enabled: true,
+        },
+        cpp: {
+            enabled: true,
+        },
+    },
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
new file mode 100644
index 0000000..c360cb8
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
@@ -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.security.keystore;
+
+import android.security.keystore.KeyAttestationApplicationId;
+
+/** @hide */
+interface IKeyAttestationApplicationIdProvider {
+    /**
+     * Provides information describing the possible applications identified by a UID.
+     * @hide
+     */
+    KeyAttestationApplicationId getKeyAttestationApplicationId(int uid);
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl
new file mode 100644
index 0000000..c33e830
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore;
+
+import android.security.keystore.KeyAttestationPackageInfo;
+
+/**
+ * @hide
+ * The information aggregated by this parcelable is used by keystore to identify a caller of the
+ * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore
+ * can only determine a caller by uid granularity, and a uid can be shared by multiple packages.
+ * The remote party must decide if it trusts all of the packages enough to consider the
+ * confidentiality of the key material in question intact.
+ */
+parcelable KeyAttestationApplicationId {
+    KeyAttestationPackageInfo[] packageInfos;
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl
new file mode 100644
index 0000000..5f647d0
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl
@@ -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 android.security.keystore;
+
+import android.security.keystore.Signature;
+
+/**
+ * @hide
+ * This parcelable constitutes and excerpt from the PackageManager's PackageInfo for the purpose of
+ * key attestation. It is part of the KeyAttestationApplicationId, which is used by
+ * keystore to identify the caller of the keystore API towards a remote party.
+ */
+parcelable KeyAttestationPackageInfo {
+    String packageName;
+
+    long versionCode;
+
+    Signature[] signatures;
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/Signature.aidl b/keystore/aaid/aidl/android/security/keystore/Signature.aidl
new file mode 100644
index 0000000..800499a
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/Signature.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.security.keystore;
+
+/**
+ * @hide
+ * Represents a signature data read from the package file. Extracted from from the PackageManager's
+ * PackageInfo for the purpose of key attestation. It is part of the KeyAttestationPackageInfo,
+ * which is used by keystore to identify the caller of the keystore API towards a remote party.
+ */
+parcelable Signature {
+    /**
+     * Represents signing certificate data associated with application package, signatures are
+     * expected to be a hex-encoded ASCII string representing valid X509 certificate.
+     */
+    byte[] data;
+}
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 31c2eb2..b7ea04f 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -128,25 +128,6 @@
     }
 
     /**
-     * Queries user state from Keystore 2.0.
-     *
-     * @param userId - Android user id of the user.
-     * @return UserState enum variant as integer if successful or an error
-     */
-    public static int getState(int userId) {
-        StrictMode.noteDiskRead();
-        try {
-            return getService().getState(userId);
-        } catch (ServiceSpecificException e) {
-            Log.e(TAG, "getState failed", e);
-            return e.errorCode;
-        } catch (Exception e) {
-            Log.e(TAG, "Can not connect to keystore", e);
-            return SYSTEM_ERROR;
-        }
-    }
-
-    /**
      * Informs Keystore 2.0 that an off body event was detected.
      */
     public static void onDeviceOffBody() {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8045f55..11b8271 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,8 +19,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.StrictMode;
-import android.os.UserHandle;
-import android.security.maintenance.UserState;
 
 /**
  * @hide This should not be made public in its present form because it
@@ -37,15 +35,6 @@
     // Used for UID field to indicate the calling UID.
     public static final int UID_SELF = -1;
 
-    // States
-    public enum State {
-        @UnsupportedAppUsage
-        UNLOCKED,
-        @UnsupportedAppUsage
-        LOCKED,
-        UNINITIALIZED
-    };
-
     private static final KeyStore KEY_STORE = new KeyStore();
 
     @UnsupportedAppUsage
@@ -55,28 +44,6 @@
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public State state(int userId) {
-        int userState = AndroidKeyStoreMaintenance.getState(userId);
-        switch (userState) {
-            case UserState.UNINITIALIZED:
-                return KeyStore.State.UNINITIALIZED;
-            case UserState.LSKF_UNLOCKED:
-                return KeyStore.State.UNLOCKED;
-            case UserState.LSKF_LOCKED:
-                return KeyStore.State.LOCKED;
-            default:
-                throw new AssertionError(userState);
-        }
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public State state() {
-        return state(UserHandle.myUserId());
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public byte[] get(String key) {
         return null;
     }
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 253d704..5825fac 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -319,7 +319,7 @@
     /**
      * Returns one of the error codes exported by the class.
      *
-     * @return a public error code, one of the values in {@link PublicErrorCode}.
+     * @return a public error code
      */
     @PublicErrorCode
     public int getNumericErrorCode() {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 96c257b..1ba41b1 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -75,16 +75,18 @@
  * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
  * interfaces.
  *
- * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
- * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not
- * support storing key pairs without a certificate. The subject, serial number, and validity dates
- * of the certificate can be customized in this spec. The self-signed certificate may be replaced at
- * a later time by a certificate signed by a Certificate Authority (CA).
+ * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android
+ * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing
+ * key pairs without a certificate. The subject, serial number, and validity dates of the
+ * certificate can be customized in this spec. The certificate may be replaced at a later time by a
+ * certificate signed by a Certificate Authority (CA).
  *
- * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
- * certificate will be created with an invalid signature which will not verify. Such a certificate
- * is still useful because it provides access to the public key. To generate a valid signature for
- * the certificate the key needs to be authorized for all of the following:
+ * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])},
+ * generated certificate may be self-signed. If a private key is not authorized to sign the
+ * certificate, then the certificate will be created with an invalid signature which will not
+ * verify. Such a certificate is still useful because it provides access to the public key. To
+ * generate a valid signature for the certificate the key needs to be authorized for all of the
+ * following:
  * <ul>
  * <li>{@link KeyProperties#PURPOSE_SIGN},</li>
  * <li>operation without requiring the user to be authenticated (see
@@ -989,12 +991,6 @@
          * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
          *        used. Attempts to use the key for any other purpose will be rejected.
          *
-         *        <p>If the set of purposes for which the key can be used does not contain
-         *        {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by
-         *        {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an
-         *        invalid signature. This is OK if the certificate is only used for obtaining the
-         *        public key from Android KeyStore.
-         *
          *        <p>See {@link KeyProperties}.{@code PURPOSE} flags.
          */
         public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
@@ -1140,7 +1136,7 @@
         }
 
         /**
-         * Sets the subject used for the self-signed certificate of the generated key pair.
+         * Sets the subject used for the certificate of the generated key pair.
          *
          * <p>By default, the subject is {@code CN=fake}.
          */
@@ -1154,7 +1150,7 @@
         }
 
         /**
-         * Sets the serial number used for the self-signed certificate of the generated key pair.
+         * Sets the serial number used for the certificate of the generated key pair.
          *
          * <p>By default, the serial number is {@code 1}.
          */
@@ -1168,8 +1164,7 @@
         }
 
         /**
-         * Sets the start of the validity period for the self-signed certificate of the generated
-         * key pair.
+         * Sets the start of the validity period for the certificate of the generated key pair.
          *
          * <p>By default, this date is {@code Jan 1 1970}.
          */
@@ -1183,8 +1178,7 @@
         }
 
         /**
-         * Sets the end of the validity period for the self-signed certificate of the generated key
-         * pair.
+         * Sets the end of the validity period for the certificate of the generated key pair.
          *
          * <p>By default, this date is {@code Jan 1 2048}.
          */
diff --git a/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt
similarity index 99%
rename from packages/SystemUI/ktfmt_includes.txt
rename to ktfmt_includes.txt
index d3254b7..e4bf4c2 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -1,3 +1,4 @@
++services/permission
 +packages/SystemUI
 -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
 -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 0000000..ff49cdc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+    // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS =
+            "androidx.window.extensions.OverlayCreateParams";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+            "androidx.window.extensions.OverlayCreateParams.taskId";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+            "androidx.window.extensions.OverlayCreateParams.tag";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+            "androidx.window.extensions.OverlayCreateParams.bounds";
+
+    private final int mTaskId;
+
+    @NonNull
+    private final String mTag;
+
+    @NonNull
+    private final Rect mBounds;
+
+    OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+        mTaskId = taskId;
+        mTag = requireNonNull(tag);
+        mBounds = requireNonNull(bounds);
+    }
+
+    int getTaskId() {
+        return mTaskId;
+    }
+
+    @NonNull
+    String getTag() {
+        return mTag;
+    }
+
+    @NonNull
+    Rect getBounds() {
+        return mBounds;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mTaskId;
+        result = 31 * result + mTag.hashCode();
+        result = 31 * result + mBounds.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) return true;
+        if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+        return mTaskId == thatParams.mTaskId
+                && mTag.equals(thatParams.mTag)
+                && mBounds.equals(thatParams.mBounds);
+    }
+
+    @Override
+    public String toString() {
+        return OverlayCreateParams.class.getSimpleName() + ": {"
+                + "taskId=" + mTaskId
+                + ", tag=" + mTag
+                + ", bounds=" + mBounds
+                + "}";
+    }
+
+    /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+    @Nullable
+    static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+        final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+        if (paramsBundle == null) {
+            return null;
+        }
+        final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+        final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+        final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+                KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+        return new OverlayCreateParams(taskId, tag, bounds);
+    }
+}
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 cdfc4c8..7743ad5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,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.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
@@ -87,6 +88,7 @@
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -123,8 +125,7 @@
      * and unregistered via {@link #clearSplitAttributesCalculator()}.
      * This is called when:
      * <ul>
-     *   <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
-     *     WindowContainerTransaction)}</li>
+     *   <li>{@link SplitPresenter#updateSplitContainer}</li>
      *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
      *   <li>Checking whether the place holder should be launched if there's a Activity matches
      *   {@link SplitPlaceholderRule} </li>
@@ -291,8 +292,8 @@
             // Resets the isolated navigation and updates the container.
             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
             final WindowContainerTransaction wct = transactionRecord.getTransaction();
-            mPresenter.setTaskFragmentIsolatedNavigation(wct,
-                    containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+            mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
+                    false /* isolated */);
             updateContainer(wct, containerToUnpin);
             transactionRecord.apply(false /* shouldApplyIndependently */);
             updateCallbackIfNecessary();
@@ -759,6 +760,8 @@
         if (targetContainer == null) {
             // When there is no embedding rule matched, try to place it in the top container
             // like a normal launch.
+            // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+            //  container.
             targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
         }
         if (targetContainer == null) {
@@ -877,19 +880,17 @@
             return true;
         }
 
-        // Skip resolving if the activity is on a pinned TaskFragmentContainer.
-        // TODO(b/243518738): skip resolving for overlay container.
-        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
-        if (container != null && taskContainer != null
-                && taskContainer.isTaskFragmentContainerPinned(container)) {
+        // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
+        if (container != null && container.isIsolatedNavigationEnabled()) {
             return true;
         }
 
+        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
         if (!isOnReparent && taskContainer != null
                 && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
                         != container) {
             // Do not resolve if the launched activity is not the top-most container (excludes
-            // the pinned container) in the Task.
+            // the pinned and overlay container) in the Task.
             return true;
         }
 
@@ -1007,6 +1008,7 @@
         if (taskContainer == null) {
             return;
         }
+        // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
         final TaskFragmentContainer targetContainer =
                 taskContainer.getTopNonFinishingTaskFragmentContainer();
         if (targetContainer == null) {
@@ -1166,7 +1168,7 @@
                 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
                 && canReuseContainer(splitRule, splitContainer.getSplitRule(),
-                        getTaskWindowMetrics(taskProperties.getConfiguration()),
+                        taskProperties.getTaskMetrics(),
                         calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
@@ -1308,15 +1310,12 @@
     @GuardedBy("mLock")
     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
-        // Skip resolving if started from pinned TaskFragmentContainer.
-        // TODO(b/243518738): skip resolving for overlay container.
+        // Skip resolving if started from an isolated navigated TaskFragmentContainer.
         if (launchingActivity != null) {
             final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
                     launchingActivity);
-            final TaskContainer taskContainer =
-                    taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
-            if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
-                    taskFragmentContainer)) {
+            if (taskFragmentContainer != null
+                    && taskFragmentContainer.isIsolatedNavigationEnabled()) {
                 return null;
             }
         }
@@ -1408,6 +1407,22 @@
     private TaskFragmentContainer createEmptyExpandedContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @Nullable Activity launchingActivity) {
+        return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+                null /* overlayTag */);
+    }
+
+    /**
+     * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+     * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+     * overlay container.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer createEmptyContainer(
+            @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+            @NonNull Rect bounds, @Nullable Activity launchingActivity,
+            @Nullable String overlayTag) {
         // 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;
@@ -1423,13 +1438,46 @@
             // Can't find any activity in the Task that we can use as the owner activity.
             return null;
         }
-        final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
-                taskId);
-        mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
-                activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
-        mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+        final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+                intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+        final IBinder taskFragmentToken = container.getTaskFragmentToken();
+        // 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);
+        final int windowingMode = taskContainer
+                .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+        mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+                sanitizedBounds, windowingMode);
+        mPresenter.updateAnimationParams(wct, taskFragmentToken,
                 TaskFragmentAnimationParams.DEFAULT);
-        return expandedContainer;
+        mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+                overlayTag != null && !sanitizedBounds.isEmpty());
+
+        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;
     }
 
     /**
@@ -1449,8 +1497,7 @@
         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
         final TaskContainer.TaskProperties taskProperties = mPresenter
                 .getTaskProperties(primaryActivity);
-        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
-                taskProperties.getConfiguration());
+        final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
         final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
                 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
                 getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1519,14 +1566,22 @@
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
-                activityInTask, taskId, null /* pairedPrimaryContainer */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
     }
 
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
-                activityInTask, taskId, null /* pairedPrimaryContainer */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+    }
+
+    @GuardedBy("mLock")
+    TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+                                       @NonNull Activity activityInTask, int taskId,
+                                       @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+        return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+                activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
     }
 
     /**
@@ -1540,11 +1595,14 @@
      * @param taskId                    parent Task of the new TaskFragment.
      * @param pairedPrimaryContainer    the paired primary {@link TaskFragmentContainer}. When it is
      *                                  set, the new container will be added right above it.
+     * @param overlayTag                The tag for the new created overlay container. It must be
+     *                                  needed if {@code isOverlay} is {@code true}. Otherwise,
+     *                                  it should be {@code null}.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
         if (activityInTask == null) {
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
@@ -1553,7 +1611,7 @@
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
-                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
         return container;
     }
 
@@ -1693,31 +1751,6 @@
     }
 
     /**
-     * Returns the topmost not finished container in Task of given task id.
-     */
-    @GuardedBy("mLock")
-    @Nullable
-    TaskFragmentContainer getTopActiveContainer(int taskId) {
-        final TaskContainer taskContainer = mTaskContainers.get(taskId);
-        if (taskContainer == null) {
-            return null;
-        }
-        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
-        for (int i = containers.size() - 1; i >= 0; i--) {
-            final TaskFragmentContainer container = containers.get(i);
-            if (!container.isFinished() && (container.getRunningActivityCount() > 0
-                    // We may be waiting for the top TaskFragment to become non-empty after
-                    // creation. In that case, we don't want to treat the TaskFragment below it as
-                    // top active, otherwise it may incorrectly launch placeholder on top of the
-                    // pending TaskFragment.
-                    || container.isWaitingActivityAppear())) {
-                return container;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Updates the presentation of the container. If the container is part of the split or should
      * have a placeholder, it will also update the other part of the split.
      */
@@ -1730,6 +1763,15 @@
             return;
         }
 
+        if (container.isFinished()) {
+            return;
+        }
+
+        if (container.isOverlay()) {
+            updateOverlayContainer(wct, container);
+            return;
+        }
+
         if (launchPlaceholderIfNecessary(wct, container)) {
             // Placeholder was launched, the positions will be updated when the activity is added
             // to the secondary container.
@@ -1750,17 +1792,35 @@
         updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
     }
 
+
+    @VisibleForTesting
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
+    void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
+                                @NonNull TaskFragmentContainer container) {
+        final TaskContainer taskContainer = container.getTaskContainer();
+        // Dismiss the overlay container if it's the only container in the task and there's no
+        // direct activity in the parent task.
+        if (taskContainer.getTaskFragmentContainers().size() == 1
+                && !taskContainer.hasDirectActivity()) {
+            container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+        }
+
+        // TODO(b/295805054): Add the logic to update overlay container
+    }
+
     /**
      * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
      * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
      * are {@code null}, the {@link SplitAttributes} will be calculated with
-     * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+     * {@link SplitPresenter#computeSplitAttributes}.
      *
      * @param splitContainer The {@link SplitContainer} to update
      * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
      *                        Otherwise, use the value calculated by
-     *                        {@link SplitPresenter#computeSplitAttributes(
-     *                        TaskContainer.TaskProperties, SplitRule, Pair)}
+     *                        {@link SplitPresenter#computeSplitAttributes}
      *
      * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
      */
@@ -1906,7 +1966,8 @@
     /** Whether or not to allow activity in this container to launch placeholder. */
     @GuardedBy("mLock")
     private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
-        final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+        final TaskFragmentContainer topContainer = container.getTaskContainer()
+                .getTopNonFinishingTaskFragmentContainer();
         if (container != topContainer) {
             // The container is not the top most.
             if (!container.isVisible()) {
@@ -2255,6 +2316,96 @@
         return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
     }
 
+    /**
+     * Gets all overlay containers from all tasks in this process, or an empty list if there's
+     * no overlay container.
+     * <p>
+     * Note that we only support one overlay container for each task, but an app could have multiple
+     * tasks.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @NonNull
+    List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+        final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+        for (int i = 0; i < mTaskContainers.size(); i++) {
+            final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+            final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+            if (overlayContainer != null) {
+                overlayContainers.add(overlayContainer);
+            }
+        }
+        return overlayContainers;
+    }
+
+    @VisibleForTesting
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(container.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull WindowContainerTransaction wct,
+            @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+            @NonNull Intent intent, @NonNull Activity launchActivity) {
+        final int taskId = overlayCreateParams.getTaskId();
+        if (taskId != launchTaskId) {
+            // The task ID doesn't match the launch activity's. Cannot determine the host task
+            // to launch the overlay.
+            throw new IllegalArgumentException("The task ID of "
+                    + "OverlayCreateParams#launchingActivity must match the task ID of "
+                    + "the activity to #startActivity with the activity options that takes "
+                    + "OverlayCreateParams.");
+        }
+        final List<TaskFragmentContainer> overlayContainers =
+                getAllOverlayTaskFragmentContainers();
+        final String overlayTag = overlayCreateParams.getTag();
+
+        // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+        // specified by Intent, expand the overlay container to fill the parent task instead.
+        final Rect bounds = overlayCreateParams.getBounds();
+        final Size minDimensions = getMinDimensions(intent);
+        final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+                minDimensions);
+        if (!overlayContainers.isEmpty()) {
+            for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+                if (!overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId == overlayContainer.getTaskId()) {
+                    // If there's an overlay container with different tag shown in the same
+                    // task, dismiss the existing overlay container.
+                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+                            wct, SplitController.this);
+                }
+                if (overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId != overlayContainer.getTaskId()) {
+                    // If there's an overlay container with same tag in a different task,
+                    // dismiss the overlay container since the tag must be unique per process.
+                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+                            wct, SplitController.this);
+                }
+                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 Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+                            .getTaskMetrics().getBounds();
+                    final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+                    final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+                    mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+                    mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+                            !sanitizedBounds.isEmpty());
+                    // 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
+                    // or tag since tags and tasks are all unique.
+                    return overlayContainer;
+                }
+            }
+        }
+        return createEmptyContainer(wct, intent, taskId,
+                (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+    }
+
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
@@ -2417,8 +2568,16 @@
                 final TaskFragmentContainer launchedInTaskFragment;
                 if (launchingActivity != null) {
                     final int taskId = getTaskId(launchingActivity);
-                    launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
-                            launchingActivity);
+                    final OverlayCreateParams overlayCreateParams =
+                            OverlayCreateParams.fromBundle(options);
+                    if (Flags.activityEmbeddingOverlayPresentationFlag()
+                            && overlayCreateParams != null) {
+                        launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+                                overlayCreateParams, taskId, intent, launchingActivity);
+                    } else {
+                        launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+                                launchingActivity);
+                    }
                 } else {
                     launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
                             intent);
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 d894487..faf7c39 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.util.DisplayMetrics;
 import android.util.LayoutDirection;
 import android.util.Pair;
 import android.util.Size;
 import android.view.View;
-import android.view.WindowInsets;
 import android.view.WindowMetrics;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@
         }
 
         final int taskId = primaryContainer.getTaskId();
-        final TaskFragmentContainer secondaryContainer = mController.newContainer(
-                null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+        final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
+                launchingActivity, taskId,
                 // Pass in the primary container to make sure it is added right above the primary.
                 primaryContainer);
         final TaskContainer taskContainer = mController.getTaskContainer(taskId);
@@ -390,14 +388,27 @@
             return;
         }
 
-        setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
-                !isStacked /* isolatedNav */);
+        setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
         if (isStacked && !splitPinRule.isSticky()) {
             secondaryContainer.getTaskContainer().removeSplitPinContainer();
         }
     }
 
     /**
+     * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+     */
+    void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+                                           @NonNull TaskFragmentContainer taskFragmentContainer,
+                                           boolean isolatedNavigationEnabled) {
+        if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+            return;
+        }
+        taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+        setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+                isolatedNavigationEnabled);
+    }
+
+    /**
      * Resizes the task fragment if it was already registered. Skips the operation if the container
      * creation has not been reported from the server yet.
      */
@@ -618,7 +629,7 @@
             @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
             @Nullable Pair<Size, Size> minDimensionsPair) {
         final Configuration taskConfiguration = taskProperties.getConfiguration();
-        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+        final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
         final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
                 mController.getSplitAttributesCalculator();
         final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +724,15 @@
         return new Size(windowLayout.minWidth, windowLayout.minHeight);
     }
 
-    private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+    static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
             @Nullable Size minDimensions) {
         if (minDimensions == null) {
             return false;
         }
+        // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+        if (bounds.isEmpty()) {
+            return false;
+        }
         return bounds.width() < minDimensions.getWidth()
                 || bounds.height() < minDimensions.getHeight();
     }
@@ -1066,14 +1081,6 @@
 
     @NonNull
     WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
-        return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
-    }
-
-    @NonNull
-    static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
-        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
-        // TODO(b/190433398): Supply correct insets.
-        final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+        return getTaskProperties(activity).getTaskMetrics();
     }
 }
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 9a0769a..eeb3ccf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArraySet;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentParentInfo;
 import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@
     @Nullable
     private SplitPinContainer mSplitPinContainer;
 
+    /** The overlay container in this Task. */
+    @Nullable
+    private TaskFragmentContainer mOverlayContainer;
+
     @NonNull
     private final Configuration mConfiguration;
 
@@ -68,6 +75,8 @@
 
     private boolean mIsVisible;
 
+    private boolean mHasDirectActivity;
+
     /**
      * TaskFragments that the organizer has requested to be closed. They should be removed when
      * the organizer receives
@@ -95,8 +104,9 @@
         mConfiguration = taskProperties.getConfiguration();
         mDisplayId = taskProperties.getDisplayId();
         // Note that it is always called when there's a new Activity is started, which implies
-        // the host task is visible.
+        // the host task is visible and has an activity in the task.
         mIsVisible = true;
+        mHasDirectActivity = true;
     }
 
     int getTaskId() {
@@ -111,6 +121,10 @@
         return mIsVisible;
     }
 
+    boolean hasDirectActivity() {
+        return mHasDirectActivity;
+    }
+
     @NonNull
     TaskProperties getTaskProperties() {
         return new TaskProperties(mDisplayId, mConfiguration);
@@ -120,6 +134,7 @@
         mConfiguration.setTo(info.getConfiguration());
         mDisplayId = info.getDisplayId();
         mIsVisible = info.isVisible();
+        mHasDirectActivity = info.hasDirectActivity();
     }
 
     /**
@@ -184,11 +199,20 @@
 
     @Nullable
     TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+        return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */);
+    }
+
+    @Nullable
+    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin,
+                                                                  boolean includeOverlay) {
         for (int i = mContainers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = mContainers.get(i);
             if (!includePin && isTaskFragmentContainerPinned(container)) {
                 continue;
             }
+            if (!includeOverlay && container.isOverlay()) {
+                continue;
+            }
             if (!container.isFinished()) {
                 return container;
             }
@@ -221,6 +245,12 @@
         return null;
     }
 
+    /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+    @Nullable
+    TaskFragmentContainer getOverlayContainer() {
+        return mOverlayContainer;
+    }
+
     int indexOf(@NonNull TaskFragmentContainer child) {
         return mContainers.indexOf(child);
     }
@@ -311,8 +341,8 @@
         onTaskFragmentContainerUpdated();
     }
 
-    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
-        mContainers.removeAll(taskFragmentContainer);
+    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+        mContainers.removeAll(taskFragmentContainers);
         onTaskFragmentContainerUpdated();
     }
 
@@ -332,6 +362,15 @@
     }
 
     private void onTaskFragmentContainerUpdated() {
+        // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+        //  another special container that should also be on top in the future.
+        updateSplitPinContainerIfNecessary();
+        // Update overlay container after split pin container since the overlay should be on top of
+        // pin container.
+        updateOverlayContainerIfNecessary();
+    }
+
+    private void updateSplitPinContainerIfNecessary() {
         if (mSplitPinContainer == null) {
             return;
         }
@@ -344,10 +383,7 @@
         }
 
         // Ensure the pinned container is top-most.
-        if (pinnedContainerIndex != mContainers.size() - 1) {
-            mContainers.remove(pinnedContainer);
-            mContainers.add(pinnedContainer);
-        }
+        moveContainerToLastIfNecessary(pinnedContainer);
 
         // Update the primary container adjacent to the pinned container if needed.
         final TaskFragmentContainer adjacentContainer =
@@ -359,6 +395,31 @@
         }
     }
 
+    private void updateOverlayContainerIfNecessary() {
+        final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+                .filter(TaskFragmentContainer::isOverlay).toList();
+        if (overlayContainers.size() > 1) {
+            throw new IllegalStateException("There must be at most one overlay container per Task");
+        }
+        mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+        if (mOverlayContainer != null) {
+            moveContainerToLastIfNecessary(mOverlayContainer);
+        }
+    }
+
+    /** Moves the {@code container} to the last to align taskFragments' z-order. */
+    private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+        final int index = mContainers.indexOf(container);
+        if (index < 0) {
+            Log.w(TAG, "The container:" + container + " is not in the container list!");
+            return;
+        }
+        if (index != mContainers.size() - 1) {
+            mContainers.remove(container);
+            mContainers.add(container);
+        }
+    }
+
     /**
      * Gets the descriptors of split states in this Task.
      *
@@ -398,6 +459,15 @@
             return mConfiguration;
         }
 
+        /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+        @NonNull
+        WindowMetrics getTaskMetrics() {
+            final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+            // TODO(b/190433398): Supply correct insets.
+            final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+            return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+        }
+
         /** Translates the given absolute bounds to relative bounds in this Task coordinate. */
         void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
             if (inOutBounds.isEmpty()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 0a694b5..3e7f99b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@
      */
     private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
 
+    @Nullable
+    private final String mOverlayTag;
+
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
 
@@ -157,15 +160,32 @@
      */
     private boolean mHasCrossProcessActivities;
 
+    /** Whether this TaskFragment enable isolated navigation. */
+    private boolean mIsIsolatedNavigationEnabled;
+
+    /**
+     * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+     * TaskFragmentContainer, String)
+     */
+    TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+                          @Nullable Intent pendingAppearedIntent,
+                          @NonNull TaskContainer taskContainer,
+                          @NonNull SplitController controller,
+                          @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+        this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+                controller, pairedPrimaryContainer, null /* overlayTag */);
+    }
+
     /**
      * Creates a container with an existing activity that will be re-parented to it in a window
      * container transaction.
      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
+     * @param overlayTag                Sets to indicate this taskFragment is an overlay container
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
             @NonNull SplitController controller,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
@@ -174,6 +194,8 @@
         mController = controller;
         mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
+        mOverlayTag = overlayTag;
+
         if (pairedPrimaryContainer != null) {
             // The TaskFragment will be positioned right above the paired container.
             if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -786,6 +808,16 @@
         mLastCompanionTaskFragment = fragmentToken;
     }
 
+    /** Returns whether to enable isolated navigation or not. */
+    boolean isIsolatedNavigationEnabled() {
+        return mIsIsolatedNavigationEnabled;
+    }
+
+    /** Sets whether to enable isolated navigation or not. */
+    void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
+        mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
+    }
+
     /**
      * Adds the pending appeared activity that has requested to be launched in this task fragment.
      * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
@@ -863,6 +895,20 @@
         return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
     }
 
+    /** Returns whether this taskFragment container is an overlay container. */
+    boolean isOverlay() {
+        return mOverlayTag != null;
+    }
+
+    /**
+     * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+     * taskFragment container is not an overlay container.
+     */
+    @Nullable
+    String getOverlayTag() {
+        return mOverlayTag;
+    }
+
     @Override
     public String toString() {
         return toString(true /* includeContainersToFinishOnExit */);
@@ -881,6 +927,7 @@
                 + " topNonFinishingActivity=" + getTopNonFinishingActivity()
                 + " runningActivityCount=" + getRunningActivityCount()
                 + " isFinished=" + mIsFinished
+                + " overlayTag=" + mOverlayTag
                 + " lastRequestedBounds=" + mLastRequestedBounds
                 + " pendingAppearedActivities=" + mPendingAppearedActivities
                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2d..4ddbd13 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "flag-junit",
         "mockito-target-extended-minus-junit4",
         "truth",
         "testables",
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
new file mode 100644
index 0000000..e74d5fb
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+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.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import com.android.window.flags.Flags;
+
+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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+    @Rule
+    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+    private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+            new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+    private SplitController.ActivityStartMonitor mMonitor;
+
+    private Intent mIntent;
+
+    private TaskFragmentContainer mOverlayContainer1;
+
+    private TaskFragmentContainer mOverlayContainer2;
+
+    private Activity mActivity;
+    @Mock
+    private Resources mActivityResources;
+
+    @Mock
+    private WindowContainerTransaction mTransaction;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+    private SplitController mSplitController;
+    private SplitPresenter mSplitPresenter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        DeviceStateManagerFoldingFeatureProducer producer =
+                mock(DeviceStateManagerFoldingFeatureProducer.class);
+        mSplitController = new SplitController(mWindowLayoutComponent, producer);
+        mSplitPresenter = mSplitController.mPresenter;
+        mMonitor = mSplitController.getActivityStartMonitor();
+        mIntent = new Intent();
+
+        spyOn(mSplitController);
+        spyOn(mSplitPresenter);
+        spyOn(mMonitor);
+
+        doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+        final Configuration activityConfig = new Configuration();
+        activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+        activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+        doReturn(activityConfig).when(mActivityResources).getConfiguration();
+        doReturn(mHandler).when(mSplitController).getHandler();
+        mActivity = createMockActivity();
+
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+    }
+
+    /** Creates a mock activity in the organizer process. */
+    @NonNull
+    private Activity createMockActivity() {
+        final Activity activity = mock(Activity.class);
+        doReturn(mActivityResources).when(activity).getResources();
+        final IBinder activityToken = new Binder();
+        doReturn(activityToken).when(activity).getActivityToken();
+        doReturn(activity).when(mSplitController).getActivity(activityToken);
+        doReturn(TASK_ID).when(activity).getTaskId();
+        doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+        return activity;
+    }
+
+    @Test
+    public void testOverlayCreateParamsFromBundle() {
+        assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+        assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+                .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+    }
+
+    @Test
+    public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+        mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+        mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+        verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+                anyInt(), any(), any());
+    }
+
+    @NonNull
+    private static Bundle createOverlayCreateParamsTestBundle() {
+        final Bundle bundle = new Bundle();
+
+        final Bundle paramsBundle = new Bundle();
+        paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+                TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+        paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+        paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+                TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+        bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+        return bundle;
+    }
+
+    @Test
+    public void testGetOverlayContainers() {
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+        final TaskFragmentContainer overlayContainer1 =
+                createTestOverlayContainer(TASK_ID, "test1");
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer1);
+
+        assertThrows(
+                "The exception must throw if there are two overlay containers in the same task.",
+                IllegalStateException.class,
+                () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+        final TaskFragmentContainer overlayContainer3 =
+                createTestOverlayContainer(TASK_ID + 1, "test3");
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer1, overlayContainer3);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+        assertThrows("The method must return null due to task mismatch between"
+                + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+                () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+                        TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is launched to the same task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+                TASK_ID + 2);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is launched with the same tag as an existing overlay container in a different "
+                + "task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+        createExistingOverlayContainers();
+
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test1", bounds),
+                TASK_ID);
+
+        assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+                + " is launched with the same tag and task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+        assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+        verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+                eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+                TASK_ID);
+
+        // OverlayContainer1 is dismissed since new container is launched in the same task with
+        // different tag. OverlayContainer2 is dismissed since new container is launched with the
+        // same tag in different task.
+        assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    private void createExistingOverlayContainers() {
+        mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+        mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+        List<TaskFragmentContainer> overlayContainers = mSplitController
+                .getAllOverlayTaskFragmentContainers();
+        assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+        mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+                MinimumDimensionActivity.class));
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+        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_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+        final Rect bounds = new Rect(TASK_BOUNDS);
+        bounds.offset(10, 10);
+        final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+                "test", bounds);
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                paramsOutsideTaskBounds, TASK_ID);
+        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(paramsOutsideTaskBounds, TASK_ID);
+
+        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+        assertThat(overlayContainer
+                .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+        assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+    }
+
+    @Test
+    public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
+        final TaskFragmentContainer overlayContainer =
+                createTestOverlayContainer(TASK_ID, "test1");
+
+        // Add a SplitPinContainer, the overlay should be on top
+        final Activity primaryActivity = createMockActivity();
+        final Activity secondaryActivity = createMockActivity();
+
+        final TaskFragmentContainer primaryContainer =
+                createMockTaskFragmentContainer(primaryActivity);
+        final TaskFragmentContainer secondaryContainer =
+                createMockTaskFragmentContainer(secondaryActivity);
+        final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+                activityActivityPair -> true /* activityPairPredicate */,
+                activityIntentPair -> true  /* activityIntentPairPredicate */,
+                parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+        mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+                secondaryContainer, splitPairRule,  splitPairRule.getDefaultSplitAttributes());
+        SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+                parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+        mSplitController.pinTopActivityStack(TASK_ID, splitPinRule);
+        final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID)
+                .getSplitPinContainer().getSecondaryContainer();
+
+        // Add a normal container after the overlay, the overlay should still on top,
+        // and the SplitPinContainer should also on top of the normal one.
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+        assertThat(taskContainer.getTaskFragmentContainers())
+                .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer)
+                .inOrder();
+
+        assertWithMessage("The pinned container must be returned excluding the overlay")
+                .that(taskContainer.getTopNonFinishingTaskFragmentContainer())
+                .isEqualTo(topPinnedContainer);
+
+        assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false))
+                .isEqualTo(container);
+
+        assertWithMessage("The overlay container must be returned since it's always on top")
+                .that(taskContainer.getTopNonFinishingTaskFragmentContainer(
+                        false /* includePin */, true /* includeOverlay */))
+                .isEqualTo(overlayContainer);
+    }
+
+    @Test
+    public void testUpdateContainer_dontInvokeUpdateOverlayForNonOverlayContainer() {
+        TaskFragmentContainer taskFragmentContainer = createMockTaskFragmentContainer(mActivity);
+
+        mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+        verify(mSplitController, never()).updateOverlayContainer(any(), any());
+    }
+
+    @Test
+    public void testUpdateOverlayContainer_dismissOverlayIfNeeded() {
+        TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+
+        mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+        final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+        assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer);
+
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY,
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+
+        mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+        assertWithMessage("The overlay must be dismissed since there's no activity"
+                + " in the task and other taskFragment.")
+                .that(taskContainer.getTaskFragmentContainers()).isEmpty();
+    }
+
+    /**
+     * A simplified version of {@link SplitController.ActivityStartMonitor
+     * #createOrUpdateOverlayTaskFragmentIfNeeded}
+     */
+    @Nullable
+    private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull OverlayCreateParams params, int taskId) {
+        return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+                taskId, mIntent, mActivity);
+    }
+
+    /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+    @NonNull
+    private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+        final TaskFragmentContainer container = mSplitController.newContainer(activity,
+                activity.getTaskId());
+        setupTaskFragmentInfo(container, activity);
+        return container;
+    }
+
+    @NonNull
+    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+        TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+                null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+                null /* pairedPrimaryContainer */, tag);
+        setupTaskFragmentInfo(overlayContainer, mActivity);
+        return overlayContainer;
+    }
+
+    private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+                                       @NonNull Activity activity) {
+        final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+        container.setInfo(mTransaction, info);
+        mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index d440a3e..02031a6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -48,18 +48,18 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import static com.google.common.truth.Truth.assertWithMessage;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
@@ -175,52 +175,6 @@
     }
 
     @Test
-    public void testGetTopActiveContainer() {
-        final TaskContainer taskContainer = createTestTaskContainer();
-        // tf1 has no running activity so is not active.
-        final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
-                new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
-        // tf2 has running activity so is active.
-        final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
-        doReturn(1).when(tf2).getRunningActivityCount();
-        taskContainer.addTaskFragmentContainer(tf2);
-        // tf3 is finished so is not active.
-        final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
-        doReturn(true).when(tf3).isFinished();
-        doReturn(false).when(tf3).isWaitingActivityAppear();
-        taskContainer.addTaskFragmentContainer(tf3);
-        mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
-
-        assertWithMessage("Must return tf2 because tf3 is not active.")
-                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
-        taskContainer.removeTaskFragmentContainer(tf3);
-
-        assertWithMessage("Must return tf2 because tf2 has running activity.")
-                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
-        taskContainer.removeTaskFragmentContainer(tf2);
-
-        assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
-                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
-        final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
-        doReturn(new ArrayList<>()).when(info).getActivities();
-        doReturn(true).when(info).isEmpty();
-        tf1.setInfo(mTransaction, info);
-
-        assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
-                + " creation.")
-                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
-        doReturn(false).when(info).isEmpty();
-        tf1.setInfo(mTransaction, info);
-
-        assertWithMessage("Must return null because tf1 becomes empty.")
-                .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
-    }
-
-    @Test
     public void testOnTaskFragmentVanished() {
         final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
         doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
@@ -305,7 +259,9 @@
 
         mSplitController.updateContainer(mTransaction, tf);
 
-        verify(mSplitController, never()).getTopActiveContainer(TASK_ID);
+        TaskContainer taskContainer = tf.getTaskContainer();
+        spyOn(taskContainer);
+        verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer();
 
         // Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
         doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -320,7 +276,7 @@
         doReturn(tf).when(splitContainer).getSecondaryContainer();
         doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
         doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
-        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        taskContainer = mSplitController.getTaskContainer(TASK_ID);
         taskContainer.addSplitContainer(splitContainer);
         // Add a mock SplitContainer on top of splitContainer
         final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -595,13 +551,12 @@
     }
 
     @Test
-    public void testResolveStartActivityIntent_skipIfPinned() {
+    public void testResolveStartActivityIntent_skipIfIsolatedNavEnabled() {
         final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
-        final TaskContainer taskContainer = container.getTaskContainer();
-        spyOn(taskContainer);
+        container.setIsolatedNavigationEnabled(true);
+
         final Intent intent = new Intent();
         setupSplitRule(mActivity, intent);
-        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
         assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
                 mActivity));
     }
@@ -634,7 +589,8 @@
                 false /* isOnReparent */);
 
         assertFalse(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
     }
 
     @Test
@@ -796,7 +752,8 @@
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -838,7 +795,8 @@
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -1181,7 +1139,7 @@
     public void testOnTransactionReady_taskFragmentParentInfoChanged() {
         final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
-                DEFAULT_DISPLAY, true);
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */);
         transaction.addChange(new TaskFragmentTransaction.Change(
                 TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
                 .setTaskId(TASK_ID)
@@ -1565,9 +1523,9 @@
         addSplitTaskFragments(primaryActivity, thirdActivity);
 
         // Ensure another SplitContainer is added and the pinned TaskFragment still on top
-        assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
-        assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
-                == secondaryActivity);
+        assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
+        assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
+                .getTopNonFinishingActivity(), secondaryActivity);
     }
 
     /** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 2188996..e3f5169 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -79,14 +79,14 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
 
         assertEquals(WINDOWING_MODE_FREEFORM,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -106,13 +106,13 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
 
         assertFalse(taskContainer.isInPictureInPicture());
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
 
         assertTrue(taskContainer.isInPictureInPicture());
     }
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c72a42c..fd4522e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -151,6 +151,7 @@
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.core_core-animation",
+        "androidx.core_core-ktx",
         "androidx.arch.core_core-runtime",
         "androidx-constraintlayout_constraintlayout",
         "androidx.dynamicanimation_dynamicanimation",
@@ -171,4 +172,5 @@
     kotlincflags: ["-Xjvm-default=all"],
     manifest: "AndroidManifest.xml",
     plugins: ["dagger2-compiler"],
+    use_resource_processor: true,
 }
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index d55a41f..51c71b1 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -5,4 +5,25 @@
     namespace: "multitasking"
     description: "An Example Flag"
     bug: "300136750"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_app_pairs"
+    namespace: "multitasking"
+    description: "Enables the ability to create and save app pairs to the Home screen"
+    bug: "274835596"
+}
+
+flag {
+    name: "desktop_windowing"
+    namespace: "multitasking"
+    description: "Enables desktop windowing"
+    bug: "304778354"
+}
+
+flag {
+    name: "enable_split_contextual"
+    namespace: "multitasking"
+    description: "Enables invoking split contextually"
+    bug: "276361926"
+}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index fa56516..c525a29 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -35,8 +35,8 @@
 
         <ImageView
             android:id="@+id/application_icon"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
+            android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+            android:layout_height="@dimen/desktop_mode_caption_icon_radius"
             android:layout_gravity="center_vertical"
             android:contentDescription="@string/app_icon_text" />
 
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index ee9f070..c6f85a0 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -22,6 +22,7 @@
     android:orientation="vertical">
 
     <LinearLayout
+        android:id="@+id/app_info_pill"
         android:layout_width="match_parent"
         android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
@@ -33,10 +34,10 @@
 
         <ImageView
             android:id="@+id/application_icon"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:layout_marginStart="14dp"
-            android:layout_marginEnd="14dp"
+            android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+            android:layout_height="@dimen/desktop_mode_caption_icon_radius"
+            android:layout_marginStart="12dp"
+            android:layout_marginEnd="12dp"
             android:contentDescription="@string/app_icon_text"/>
 
         <TextView
@@ -66,6 +67,7 @@
     </LinearLayout>
 
     <LinearLayout
+        android:id="@+id/windowing_pill"
         android:layout_width="match_parent"
         android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
@@ -116,6 +118,7 @@
     </LinearLayout>
 
     <LinearLayout
+        android:id="@+id/more_actions_pill"
         android:layout_width="match_parent"
         android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index da8abde..8d2e28b 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -45,13 +45,13 @@
     <integer name="config_pipForceCloseDelay">5000</integer>
 
     <!-- Animation duration when exit starting window: fade out icon -->
-    <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer>
+    <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer>
 
     <!-- Animation delay when exit starting window: reveal app -->
-    <integer name="starting_window_app_reveal_anim_delay">0</integer>
+    <integer name="starting_window_app_reveal_anim_delay">200</integer>
 
     <!-- Animation duration when exit starting window: reveal app -->
-    <integer name="starting_window_app_reveal_anim_duration">500</integer>
+    <integer name="starting_window_app_reveal_anim_duration">300</integer>
 
     <!-- Default animation type when hiding the starting window. The possible values are:
           - 0 for radial vanish + slide up
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 97a9d48..7436400 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -58,6 +58,9 @@
          if a custom action is present before closing it. -->
     <integer name="config_pipForceCloseDelay">1000</integer>
 
+    <!-- Allow PIP to resize via pinch gesture. -->
+    <bool name="config_pipEnablePinchResize">true</bool>
+
     <!-- Animation duration when using long press on recents to dock -->
     <integer name="long_press_dock_anim_duration">250</integer>
 
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1f6f7ae..c4be384 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -454,6 +454,9 @@
     <!-- The radius of the caption menu corners. -->
     <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
 
+    <!-- The radius of the caption menu icon. -->
+    <dimen name="desktop_mode_caption_icon_radius">28dp</dimen>
+
     <!-- The radius of the caption menu shadow. -->
     <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 34bf9e0..2e5448a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 
+import android.annotation.SuppressLint;
 import android.app.WindowConfiguration;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
@@ -29,6 +30,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -44,9 +46,14 @@
     /** Display area leashes, which is mapped by display IDs. */
     private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
 
-    public RootDisplayAreaOrganizer(Executor executor) {
+    public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) {
         super(executor);
-        List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    @SuppressLint("MissingPermission") // Only called by SysUI.
+    private void onInit() {
+        final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
         for (int i = infos.size() - 1; i >= 0; --i) {
             onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 38550b4..ab61a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 
+import android.annotation.SuppressLint;
 import android.annotation.UiContext;
 import android.app.ResourcesManager;
 import android.content.Context;
@@ -38,6 +39,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -69,10 +71,17 @@
 
     private final Context mContext;
 
-    public RootTaskDisplayAreaOrganizer(Executor executor, Context context) {
+    public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context,
+            @NonNull ShellInit shellInit) {
         super(executor);
         mContext = context;
-        List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    @SuppressLint("MissingPermission") // Only called by SysUI.
+    private void onInit() {
+        final List<DisplayAreaAppearedInfo> infos =
+                registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
         for (int i = infos.size() - 1; i >= 0; --i) {
             onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 06ce371..8cf869b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -87,33 +87,28 @@
         mTransitions.addHandler(this);
     }
 
-    @Override
-    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        boolean containsEmbeddingSplit = false;
-        boolean containsNonEmbeddedChange = false;
-        final List<TransitionInfo.Change> changes = info.getChanges();
-        for (int i = changes.size() - 1; i >= 0; i--) {
-            final TransitionInfo.Change change = changes.get(i);
-            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
-                containsNonEmbeddedChange = true;
-            } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
+    /** Whether ActivityEmbeddingController should animate this transition. */
+    public boolean shouldAnimate(@NonNull TransitionInfo info) {
+        boolean containsEmbeddingChange = false;
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
+                    FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
                 // Whether the Task contains any ActivityEmbedding split before or after the
                 // transition.
-                containsEmbeddingSplit = true;
+                containsEmbeddingChange = true;
             }
         }
-        if (!containsEmbeddingSplit) {
+        if (!containsEmbeddingChange) {
             // Let the system to play the default animation if there is no ActivityEmbedding split
             // window. This allows to play the app customized animation when there is no embedding,
             // such as the device is in a folded state.
             return false;
         }
-        if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+
+        if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
             return false;
         }
+
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         if (options != null
                 // Scene-transition will be handled by app side.
@@ -123,6 +118,17 @@
             return false;
         }
 
+        return true;
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+        if (!shouldAnimate(info)) return false;
+
         // Start ActivityEmbedding animation.
         mTransitionCallbacks.put(transition, finishCallback);
         mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +142,16 @@
         mAnimationRunner.cancelAnimationFromMerge();
     }
 
+    /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
+    private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
         final Rect nonClosingEmbeddedArea = new Rect();
         for (int i = changes.size() - 1; i >= 0; i--) {
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 3790f04..d08c573 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.back;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
 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;
@@ -70,7 +71,6 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
-
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -317,7 +317,11 @@
             executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
                     (controller) -> controller.registerAnimation(
                             BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                            new BackAnimationRunner(callback, runner)));
+                            new BackAnimationRunner(
+                                    callback,
+                                    runner,
+                                    controller.mContext,
+                                    CUJ_PREDICTIVE_BACK_HOME)));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 431df21..dc413b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -27,16 +28,22 @@
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
+
 /**
  * Used to register the animation callback and runner, it will trigger result if gesture was finish
  * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
  * trigger the real back behavior.
  */
 public class BackAnimationRunner {
+    private static final int NO_CUJ = -1;
     private static final String TAG = "ShellBackPreview";
 
     private final IOnBackInvokedCallback mCallback;
     private final IRemoteAnimationRunner mRunner;
+    private final @InteractionJankMonitor.CujType int mCujType;
+    private final Context mContext;
 
     // Whether we are waiting to receive onAnimationStart
     private boolean mWaitingAnimation;
@@ -45,9 +52,21 @@
     private boolean mAnimationCancelled;
 
     public BackAnimationRunner(
-            @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
+            @NonNull IOnBackInvokedCallback callback,
+            @NonNull IRemoteAnimationRunner runner,
+            @NonNull Context context,
+            @InteractionJankMonitor.CujType int cujType) {
         mCallback = callback;
         mRunner = runner;
+        mCujType = cujType;
+        mContext = context;
+    }
+
+    public BackAnimationRunner(
+            @NonNull IOnBackInvokedCallback callback,
+            @NonNull IRemoteAnimationRunner runner,
+            @NonNull Context context) {
+        this(callback, runner, context, NO_CUJ);
     }
 
     /** Returns the registered animation runner */
@@ -70,10 +89,17 @@
                 new IRemoteAnimationFinishedCallback.Stub() {
                     @Override
                     public void onAnimationFinished() {
+                        if (shouldMonitorCUJ(apps)) {
+                            InteractionJankMonitorUtils.endTracing(mCujType);
+                        }
                         finishedCallback.run();
                     }
                 };
         mWaitingAnimation = false;
+        if (shouldMonitorCUJ(apps)) {
+            InteractionJankMonitorUtils.beginTracing(
+                    mCujType, mContext, apps[0].leash, /* tag */ null);
+        }
         try {
             getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
                     nonApps, callback);
@@ -82,6 +108,10 @@
         }
     }
 
+    private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+        return apps.length > 0 && mCujType != NO_CUJ;
+    }
+
     void startGesture() {
         mWaitingAnimation = true;
         mAnimationCancelled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 114486e..24479d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -19,6 +19,7 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
 import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
@@ -135,7 +136,8 @@
     @Inject
     public CrossActivityAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackAnimationRunner = new BackAnimationRunner(
+                new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
         mBackground = background;
         mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
         mEnteringProgressSpring.setSpring(new SpringForce()
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 209d853..fc5ff01 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
@@ -20,6 +20,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.window.BackEvent.EDGE_RIGHT;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
 import android.animation.Animator;
@@ -108,6 +109,7 @@
     private final float[] mTmpTranslate = {0, 0, 0};
     private final BackAnimationRunner mBackAnimationRunner;
     private final BackAnimationBackground mBackground;
+    private final Context mContext;
     private RemoteAnimationTarget mEnteringTarget;
     private RemoteAnimationTarget mClosingTarget;
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -120,8 +122,10 @@
 
     @Inject
     public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+        mContext = context;
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackAnimationRunner = new BackAnimationRunner(
+                new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
         mBackground = background;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index aca638c..5254ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -19,6 +19,7 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
 import android.animation.Animator;
@@ -97,7 +98,8 @@
             SurfaceControl.Transaction transaction, Choreographer choreographer) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mBackground = background;
-        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackAnimationRunner = new BackAnimationRunner(
+                new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
         mCustomAnimationLoader = new CustomAnimationLoader(context);
 
         mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
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 dddcbd4..f0da35d 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
@@ -317,7 +317,8 @@
         mBubbleIconFactory = new BubbleIconFactory(context,
                 context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
                 context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
-                context.getResources().getColor(R.color.important_conversation),
+                context.getResources().getColor(
+                        com.android.launcher3.icons.R.color.important_conversation),
                 context.getResources().getDimensionPixelSize(
                         com.android.internal.R.dimen.importance_ring_stroke_width));
         mDisplayController = displayController;
@@ -949,7 +950,8 @@
         mBubbleIconFactory = new BubbleIconFactory(mContext,
                 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
                 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
-                mContext.getResources().getColor(R.color.important_conversation),
+                mContext.getResources().getColor(
+                        com.android.launcher3.icons.R.color.important_conversation),
                 mContext.getResources().getDimensionPixelSize(
                         com.android.internal.R.dimen.importance_ring_stroke_width));
 
@@ -988,7 +990,8 @@
                 mBubbleIconFactory = new BubbleIconFactory(mContext,
                         mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
                         mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
-                        mContext.getResources().getColor(R.color.important_conversation),
+                        mContext.getResources().getColor(
+                                com.android.launcher3.icons.R.color.important_conversation),
                         mContext.getResources().getDimensionPixelSize(
                                 com.android.internal.R.dimen.importance_ring_stroke_width));
                 mStackView.onDisplaySizeChanged();
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 dc099d9..22e836a 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
@@ -113,7 +113,7 @@
                 context,
                 res.getDimensionPixelSize(R.dimen.bubble_size),
                 res.getDimensionPixelSize(R.dimen.bubble_badge_size),
-                res.getColor(R.color.important_conversation),
+                res.getColor(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 2241c34..3660fa2 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
@@ -57,6 +57,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
+import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
 import android.view.WindowManagerPolicyConstants;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -212,7 +213,8 @@
     private ExpandedViewAnimationController mExpandedViewAnimationController;
 
     private View mScrim;
-    private boolean mScrimAnimating;
+    @Nullable
+    private ViewPropertyAnimator mScrimAnimation;
     private View mManageMenuScrim;
     private FrameLayout mExpandedViewContainer;
 
@@ -748,8 +750,8 @@
             float collapsed = -Math.min(dy, 0);
             mExpandedViewAnimationController.updateDrag((int) collapsed);
 
-            // Update scrim
-            if (!mScrimAnimating) {
+            // Update scrim if it's not animating already
+            if (mScrimAnimation == null) {
                 mScrim.setAlpha(getScrimAlphaForDrag(collapsed));
             }
         }
@@ -768,8 +770,8 @@
             } else {
                 mExpandedViewAnimationController.animateBackToExpanded();
 
-                // Update scrim
-                if (!mScrimAnimating) {
+                // Update scrim if it's not animating already
+                if (mScrimAnimation == null) {
                     showScrim(true, null /* runnable */);
                 }
             }
@@ -1784,13 +1786,14 @@
             mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
             mStackAnimationController.setStackPosition(startPosition);
             mExpandedAnimationController.setCollapsePoint(startPosition);
-            // Set the translation x so that this bubble will animate in from the same side they
-            // expand / collapse on.
-            bubble.getIconView().setTranslationX(startPosition.x);
         } else if (firstBubble) {
             mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
         }
 
+        // Set the view translation x so that this bubble will animate in from the same side they
+        // expand / collapse on.
+        bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x);
+
         mBubbleContainer.addView(bubble.getIconView(), 0,
                 new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
                         mPositioner.getBubbleSize()));
@@ -2236,26 +2239,27 @@
     private void showScrim(boolean show, Runnable after) {
         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
             @Override
-            public void onAnimationStart(Animator animation) {
-                mScrimAnimating = true;
-            }
-
-            @Override
             public void onAnimationEnd(Animator animation) {
-                mScrimAnimating = false;
+                mScrimAnimation = null;
                 if (after != null) {
                     after.run();
                 }
             }
         };
+        if (mScrimAnimation != null) {
+            // Cancel scrim animation if it animates
+            mScrimAnimation.cancel();
+        }
         if (show) {
-            mScrim.animate()
+            mScrimAnimation = mScrim.animate();
+            mScrimAnimation
                     .setInterpolator(ALPHA_IN)
                     .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
                     .setListener(listener)
                     .start();
         } else {
-            mScrim.animate()
+            mScrimAnimation = mScrim.animate();
+            mScrimAnimation
                     .alpha(0f)
                     .setInterpolator(ALPHA_OUT)
                     .setListener(listener)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 738c94e..79f306e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,6 +19,7 @@
 import static android.view.View.LAYOUT_DIRECTION_RTL;
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
 
 import android.content.res.Resources;
 import android.graphics.Path;
@@ -375,6 +376,9 @@
         mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
         mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
         mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+        int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels;
+        mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent(
+                getFlingToDismissTargetWidth(screenWidthPx));
     }
 
     private void springBubbleTo(View bubble, float x, float y) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
new file mode 100644
index 0000000..2a44f04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.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.wm.shell.bubbles.animation
+
+/** Utils related to the fling to dismiss animation. */
+object FlingToDismissUtils {
+
+    /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */
+    private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f
+    /**
+     * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in
+     * portrait.
+     */
+    private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f
+    /**
+     * The target width surrounding the dismiss target on a large width screen, e.g. tablet in
+     * landscape.
+     */
+    private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f
+
+    /** Returns the dismiss target width for the specified [screenWidthPx]. */
+    @JvmStatic
+    fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when {
+        screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE
+        screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM
+        else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL
+    }
+}
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 aad2683..e487328 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
@@ -17,6 +17,7 @@
 package com.android.wm.shell.bubbles.animation;
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
 
 import android.content.ContentResolver;
 import android.content.res.Resources;
@@ -851,6 +852,15 @@
         if (mLayout != null) {
             Resources res = mLayout.getContext().getResources();
             mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+            updateFlingToDismissTargetWidth();
+        }
+    }
+
+    private void updateFlingToDismissTargetWidth() {
+        if (mLayout != null && mMagnetizedStack != null) {
+            int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels;
+            mMagnetizedStack.setFlingToTargetWidthPercent(
+                    getFlingToDismissTargetWidth(screenWidthPx));
         }
     }
 
@@ -1022,23 +1032,8 @@
             };
             mMagnetizedStack.setHapticsEnabled(true);
             mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+            updateFlingToDismissTargetWidth();
         }
-
-        final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
-        final float minVelocity = Settings.Secure.getFloat(contentResolver,
-                "bubble_dismiss_fling_min_velocity",
-                mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
-        final float maxVelocity = Settings.Secure.getFloat(contentResolver,
-                "bubble_dismiss_stick_max_velocity",
-                mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
-        final float targetWidth = Settings.Secure.getFloat(contentResolver,
-                "bubble_dismiss_target_width_percent",
-                mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
-        mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
-        mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
-        mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
         return mMagnetizedStack;
     }
 
@@ -1053,7 +1048,7 @@
      * property directly to move the first bubble and cause the stack to 'follow' to the new
      * location.
      *
-     * This could also be achieved by simply animating the first bubble view and adding an update
+     * <p>This could also be achieved by simply animating the first bubble view and adding an update
      * listener to dispatch movement to the rest of the stack. However, this would require
      * duplication of logic in that update handler - it's simpler to keep all logic contained in the
      * {@link #moveFirstBubbleWithStackFollowing} method.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
index 7af0389..6519eee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -1 +1,2 @@
 madym@google.com
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 931cf0c..c6c9b35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -94,6 +94,10 @@
         mCurrentIcon = icon;
         // Remove old image while waiting for the new one to load.
         mIconImageView.setImageDrawable(null);
+        if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+            // Disallow loading icon from content URI
+            return;
+        }
         icon.loadDrawableAsync(mContext, d -> {
             // The image hasn't been set any other way and the drawable belongs to the most
             // recently set Icon.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 3b32b6c..d520ff7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -126,12 +126,22 @@
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
     private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
 
+    // the size of the current bounds relative to the max size spec
+    private float mBoundsScale;
+
     public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
         mContext = context;
         reloadResources();
         mSizeSpecSource = sizeSpecSource;
         mPipDisplayLayoutState = pipDisplayLayoutState;
+
+        // Update the relative proportion of the bounds compared to max possible size. Max size
+        // spec takes the aspect ratio of the bounds into account, so both width and height
+        // scale by the same factor.
+        addPipExclusionBoundsChangeCallback((bounds) -> {
+            mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+        });
     }
 
     /** Reloads the resources. */
@@ -160,6 +170,15 @@
         return new Rect(mBounds);
     }
 
+    /**
+     * Get the scale of the current bounds relative to the maximum size possible.
+     *
+     * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}.
+     */
+    public float getBoundsScale() {
+        return mBoundsScale;
+    }
+
     /** Returns the current movement bounds. */
     @NonNull
     public Rect getMovementBounds() {
@@ -622,6 +641,9 @@
         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
         pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
         pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
+        pw.println(innerPrefix + "mMinSize=" + mMinSize);
+        pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
+        pw.println(innerPrefix + "mBoundsScale" + mBoundsScale);
         if (mPipReentryState == null) {
             pw.println(innerPrefix + "mPipReentryState=null");
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e6d3abc..ea7b2e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -110,13 +110,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -605,6 +605,7 @@
     @Provides
     static Transitions provideTransitions(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             ShellTaskOrganizer organizer,
             TransactionPool pool,
@@ -612,14 +613,13 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor,
-            ShellCommandHandler shellCommandHandler,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
         if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
             // TODO(b/238217847): Force override shell init if registration is disabled
             shellInit = new ShellInit(mainExecutor);
         }
-        return new Transitions(context, shellInit, shellController, organizer, pool,
-                displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler,
+        return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
+                pool, displayController, mainExecutor, mainHandler, animExecutor,
                 rootTaskDisplayAreaOrganizer);
     }
 
@@ -658,15 +658,15 @@
     @WMSingleton
     @Provides
     static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer(
-            @ShellMainThread ShellExecutor mainExecutor, Context context) {
-        return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
+            @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) {
+        return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit);
     }
 
     @WMSingleton
     @Provides
     static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new RootDisplayAreaOrganizer(mainExecutor);
+            @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) {
+        return new RootDisplayAreaOrganizer(mainExecutor, shellInit);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 5dfba5e..a533ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,6 +32,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -203,6 +204,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
+            DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
@@ -218,6 +220,7 @@
                     taskOrganizer,
                     displayController,
                     shellController,
+                    displayInsetsController,
                     syncQueue,
                     transitions,
                     desktopTasksController,
@@ -364,11 +367,12 @@
             KeyguardTransitionHandler keyguardTransitionHandler,
             Optional<DesktopTasksController> desktopTasksController,
             Optional<UnfoldTransitionHandler> unfoldHandler,
+            Optional<ActivityEmbeddingController> activityEmbeddingController,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
                 pipTransitionController, recentsTransitionHandler,
                 keyguardTransitionHandler, desktopTasksController,
-                unfoldHandler);
+                unfoldHandler, activityEmbeddingController);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 0000000..74a29dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
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 8a64037..1898ea7 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
@@ -17,19 +17,28 @@
 package com.android.wm.shell.dagger.pip;
 
 import android.annotation.NonNull;
+import android.content.Context;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
  * the successor of its sibling {@link Pip1Module}.
@@ -42,8 +51,26 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm) {
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            Optional<PipController> pipController) {
         return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
                 pipBoundsAlgorithm);
     }
+
+    @WMSingleton
+    @Provides
+    static Optional<PipController> providePipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            return Optional.empty();
+        } else {
+            return Optional.ofNullable(PipController.create(
+                    context, shellInit, shellController, displayController, displayInsetsController,
+                    pipDisplayLayoutState));
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 1064867..63f20fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -797,21 +797,15 @@
                     mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
                     mPipBoundsState.getStashedState());
 
-            // Scale PiP on density dpi change, so it appears to be the same size physically.
-            final boolean densityDpiChanged =
-                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
-                    && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
-                            != layout.densityDpi());
-            if (densityDpiChanged) {
-                final float scale = (float) layout.densityDpi()
-                        / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
-                postChangeBounds.set(0, 0,
-                        (int) (postChangeBounds.width() * scale),
-                        (int) (postChangeBounds.height() * scale));
-            }
-
             updateDisplayLayout.run();
 
+            // Resize the PiP bounds to be at the same scale relative to the new size spec. For
+            // example, if PiP was resized to 90% of the maximum size on the previous layout,
+            // make sure it is 90% of the new maximum size spec.
+            postChangeBounds.set(0, 0,
+                    (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+                    (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+
             // Calculate the PiP bounds in the new orientation based on same fraction along the
             // rotated movement bounds.
             final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -822,6 +816,15 @@
                     mPipDisplayLayoutState.getDisplayBounds(),
                     mPipDisplayLayoutState.getDisplayLayout().stableInsets());
 
+            // make sure we user resize to the updated bounds to avoid animating to any outdated
+            // sizes from the previous layout upon double tap CUJ
+            mPipBoundsState.setHasUserResizedPip(true);
+            mTouchHandler.setUserResizeBounds(postChangeBounds);
+
+            final boolean densityDpiChanged =
+                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+                            && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+                            != layout.densityDpi());
             if (densityDpiChanged) {
                 // Using PipMotionHelper#movePip directly here may cause race condition since
                 // the app content in PiP mode may or may not be updated for the new density dpi.
@@ -833,15 +836,6 @@
                 // Directly move PiP to its final destination bounds without animation.
                 mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
             }
-
-            // if the pip window size is beyond allowed bounds user resize to normal bounds
-            if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x
-                    || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x
-                    || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y
-                    || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) {
-                mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction);
-            }
-
         } else {
             updateDisplayLayout.run();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index e5f9fdc..f175775 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,7 +15,6 @@
  */
 package com.android.wm.shell.pip.phone;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
@@ -31,7 +30,6 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Looper;
-import android.provider.DeviceConfig;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
 import android.view.InputChannel;
@@ -155,21 +153,8 @@
         mContext.getDisplay().getRealSize(mMaxSize);
         reloadResources();
 
-        mEnablePinchResize = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                PIP_PINCH_RESIZE,
-                /* defaultValue = */ true);
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mMainExecutor,
-                new DeviceConfig.OnPropertiesChangedListener() {
-                    @Override
-                    public void onPropertiesChanged(DeviceConfig.Properties properties) {
-                        if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
-                            mEnablePinchResize = properties.getBoolean(
-                                    PIP_PINCH_RESIZE, /* defaultValue = */ true);
-                        }
-                    }
-                });
+        final Resources res = mContext.getResources();
+        mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
     }
 
     public void onConfigurationChanged() {
@@ -579,6 +564,12 @@
                     resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
                 }
 
+                // If user resize is smaller than min size, auto resize to min
+                if (mLastResizeBounds.width() < mMinSize.x
+                        || mLastResizeBounds.height() < mMinSize.y) {
+                    resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+                }
+
                 // get the current movement bounds
                 final Rect movementBounds = mPipBoundsAlgorithm
                         .getMovementBounds(mLastResizeBounds);
@@ -679,6 +670,8 @@
         pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
         pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
         pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
+        pw.println(innerPrefix + "mMinSize=" + mMinSize);
+        pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
     }
 
     class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
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 2ce4fb9..452a416 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
@@ -779,13 +779,10 @@
     }
 
     /**
-     * Resizes the pip window and updates user resized bounds
-     *
-     * @param bounds target bounds to resize to
-     * @param snapFraction snap fraction to apply after resizing
+     * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
      */
-    void userResizeTo(Rect bounds, float snapFraction) {
-        mPipResizeGestureHandler.userResizeTo(bounds, snapFraction);
+    void setUserResizeBounds(Rect bounds) {
+        mPipResizeGestureHandler.setUserResizeBounds(bounds);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+        DisplayController.OnDisplaysChangedListener {
+    private static final String TAG = PipController.class.getSimpleName();
+
+    private Context mContext;
+    private ShellController mShellController;
+    private DisplayController mDisplayController;
+    private DisplayInsetsController mDisplayInsetsController;
+    private PipDisplayLayoutState mPipDisplayLayoutState;
+
+    private PipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        mContext = context;
+        mShellController = shellController;
+        mDisplayController = displayController;
+        mDisplayInsetsController = displayInsetsController;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        // Ensure that we have the display info in case we get calls to update the bounds before the
+        // listener calls back
+        mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+        DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+
+        mShellController.addConfigurationChangeListener(this);
+        mDisplayController.addDisplayWindowListener(this);
+        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+                new DisplayInsetsController.OnInsetsChangedListener() {
+                    @Override
+                    public void insetsChanged(InsetsState insetsState) {
+                        onDisplayChanged(mDisplayController
+                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+                    }
+                });
+    }
+
+    /**
+     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+     */
+    public static PipController create(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Device doesn't support Pip feature", TAG);
+            return null;
+        }
+        return new PipController(context, shellInit, shellController, displayController,
+                displayInsetsController, pipDisplayLayoutState);
+    }
+
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfiguration) {
+        mPipDisplayLayoutState.onConfigurationChanged();
+    }
+
+    @Override
+    public void onThemeChanged() {
+        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    private void onDisplayChanged(DisplayLayout layout) {
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+    }
+}
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 d31476c..d277eef 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
@@ -925,19 +925,8 @@
                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                 else wct.restoreTransientOrder(mRecentsTask);
             }
-            if (!toHome
-                    // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
-                    // live tile (pausing app). If the gesture is "cancelled" we need to return to
-                    // 3p launcher instead of "task-switching" away from it.
-                    && (!mWillFinishToHome || mPausingSeparateHome)
-                    && mPausingTasks != null && mState == STATE_NORMAL) {
-                if (mPausingSeparateHome) {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  returning to 3p home");
-                } else {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  returning to app");
-                }
+            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");
                 // The gesture is returning to the pausing-task(s) rather than continuing with
                 // recents, so end the transition by moving the app back to the top (and also
                 // re-showing it's task).
@@ -969,6 +958,15 @@
                     wct.restoreTransientOrder(mRecentsTask);
                 }
             } else {
+                if (mPausingSeparateHome) {
+                    if (mOpeningTasks.isEmpty()) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  recents occluded 3p home");
+                    } else {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  switch task by recents on 3p home");
+                    }
+                }
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  normal finish");
                 // The general case: committing to recents, going home, or switching tasks.
                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
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 b294866..7a4834c 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
@@ -23,6 +23,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -395,6 +396,13 @@
         return mMainStage.isActive();
     }
 
+    /** @return whether this transition-request has the launch-adjacent flag. */
+    public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) {
+        final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+        return triggerTask != null && triggerTask.baseIntent != null
+                && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0;
+    }
+
     /** @return whether the transition-request implies entering pip from split. */
     public boolean requestImpliesSplitToPip(TransitionRequestInfo request) {
         if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) {
@@ -2242,6 +2250,25 @@
         return SPLIT_POSITION_UNDEFINED;
     }
 
+    /**
+     * Returns the {@link StageType} where {@param token} is being used
+     * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise
+     */
+    @StageType
+    public int getSplitItemStage(@Nullable WindowContainerToken token) {
+        if (token == null) {
+            return STAGE_TYPE_UNDEFINED;
+        }
+
+        if (mMainStage.containsToken(token)) {
+            return STAGE_TYPE_MAIN;
+        } else if (mSideStage.containsToken(token)) {
+            return STAGE_TYPE_SIDE;
+        }
+
+        return STAGE_TYPE_UNDEFINED;
+    }
+
     @Override
     public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
         final StageTaskListener topLeftStage =
@@ -2440,10 +2467,20 @@
                         EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
             }
 
-            // When split in the background, it should be only opening/dismissing transition and
-            // would keep out not empty. Prevent intercepting all transitions for split screen when
-            // it is in the background and not identify to handle it.
-            return (!out.isEmpty() || isSplitScreenVisible()) ? out : null;
+            if (!out.isEmpty()) {
+                // One of the cases above handled it
+                return out;
+            } else if (isSplitScreenVisible()) {
+                // If split is visible, only defer handling this transition if it's launching
+                // adjacent while there is already a split pair -- this may trigger PIP and
+                // that should be handled by the mixed handler.
+                final boolean deferTransition = requestHasLaunchAdjacentFlag(request)
+                    && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0;
+                return !deferTransition ? out : null;
+            }
+            // Don't intercept the transition if we are not handling it as a part of one of the
+            // cases above and it is not already visible
+            return null;
         } else {
             if (isOpening && getStageOfTask(triggerTask) != null) {
                 // One task is appearing into split, prepare to enter split screen.
@@ -2479,7 +2516,16 @@
                 mRecentTasks.ifPresent(
                         recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
             }
-            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
+            @StageType int topStage = STAGE_TYPE_UNDEFINED;
+            if (isSplitScreenVisible()) {
+                // Get the stage where a child exists to keep that stage onTop
+                if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) {
+                    topStage = STAGE_TYPE_MAIN;
+                } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) {
+                    topStage = STAGE_TYPE_SIDE;
+                }
+            }
+            prepareExitSplitScreen(topStage, outWCT);
         }
     }
 
@@ -2693,7 +2739,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         boolean shouldAnimate = true;
         if (mSplitTransitions.isPendingEnter(transition)) {
-            shouldAnimate = startPendingEnterAnimation(
+            shouldAnimate = startPendingEnterAnimation(transition,
                     mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
@@ -2732,7 +2778,7 @@
         }
     }
 
-    private boolean startPendingEnterAnimation(
+    private boolean startPendingEnterAnimation(@NonNull IBinder transition,
             @NonNull SplitScreenTransitions.EnterSession enterTransition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
@@ -2761,21 +2807,22 @@
             }
         }
 
-        if (mSplitTransitions.mPendingEnter.mExtraTransitType
+        SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter;
+        if (pendingEnter.mExtraTransitType
                 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
             // Open to side should only be used when split already active and foregorund.
             if (mainChild == null && sideChild == null) {
                 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
                         "Launched a task in split, but didn't receive any task in transition."));
                 // This should happen when the target app is already on front, so just cancel.
-                mSplitTransitions.mPendingEnter.cancel(null);
+                pendingEnter.cancel(null);
                 return true;
             }
         } else {
             if (mainChild == null || sideChild == null) {
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
-                mSplitTransitions.mPendingEnter.cancel(
+                pendingEnter.cancel(
                         (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
                 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
                         "launched 2 tasks in split, but didn't receive "
@@ -2786,6 +2833,12 @@
                 if (mRecentTasks.isPresent() && sideChild != null) {
                     mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
                 }
+                if (pendingEnter.mRemoteHandler != null) {
+                    // Pass false for aborted since WM didn't abort, business logic chose to
+                    // terminate/exit early
+                    pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
+                            false /*aborted*/, finishT);
+                }
                 mSplitUnsupportedToast.show();
                 return true;
             }
@@ -2896,7 +2949,11 @@
         return SPLIT_POSITION_UNDEFINED;
     }
 
-    /** Synchronize split-screen state with transition and make appropriate preparations. */
+    /**
+     * Synchronize split-screen state with transition and make appropriate preparations.
+     * @param toStage The stage that will not be dismissed. If set to
+     *        {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed
+     */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
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 e828eed..918a5a4 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,7 +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_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;
@@ -43,6 +45,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -50,6 +53,7 @@
 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;
@@ -73,6 +77,7 @@
     private final KeyguardTransitionHandler mKeyguardHandler;
     private DesktopTasksController mDesktopTasksController;
     private UnfoldTransitionHandler mUnfoldHandler;
+    private ActivityEmbeddingController mActivityEmbeddingController;
 
     private static class MixedTransition {
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -92,9 +97,12 @@
         /** Recents Transition while in desktop mode. */
         static final int TYPE_RECENTS_DURING_DESKTOP = 6;
 
-        /** Fuld/Unfold transition. */
+        /** Fold/Unfold transition. */
         static final int TYPE_UNFOLD = 7;
 
+        /** Enter pip from one of the Activity Embedding windows. */
+        static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
@@ -149,7 +157,8 @@
             Optional<RecentsTransitionHandler> recentsHandlerOptional,
             KeyguardTransitionHandler keyguardHandler,
             Optional<DesktopTasksController> desktopTasksControllerOptional,
-            Optional<UnfoldTransitionHandler> unfoldHandler) {
+            Optional<UnfoldTransitionHandler> unfoldHandler,
+            Optional<ActivityEmbeddingController> activityEmbeddingController) {
         mPlayer = player;
         mKeyguardHandler = keyguardHandler;
         if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -169,6 +178,7 @@
                 }
                 mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
                 mUnfoldHandler = unfoldHandler.orElse(null);
+                mActivityEmbeddingController = activityEmbeddingController.orElse(null);
             }, this);
         }
     }
@@ -191,6 +201,16 @@
             mPipHandler.augmentRequest(transition, request, out);
             mSplitHandler.addEnterOrExitIfNeeded(request, out);
             return out;
+        } else if (request.getType() == TRANSIT_PIP
+                && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
+                mActivityEmbeddingController != null)) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    " Got a PiP-enter request from an Activity Embedding split");
+            mActiveTransitions.add(new MixedTransition(
+                    MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
+            // Postpone transition splitting to later.
+            WindowContainerTransaction out = new WindowContainerTransaction();
+            return out;
         } else if (request.getRemoteTransition() != null
                 && TransitionUtil.isOpeningType(request.getType())
                 && (request.getTriggerTask() == null
@@ -344,6 +364,8 @@
                     // Keyguard handler cannot handle it, process through original mixed
                     mActiveTransitions.remove(keyguardMixed);
                 }
+            } else if (mPipHandler != null) {
+                mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
             }
         }
 
@@ -352,6 +374,9 @@
         if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
             return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
                     finishCallback);
+        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+            return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
+                    finishTransaction, finishCallback);
         } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
             return false;
         } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -397,6 +422,58 @@
         }
     }
 
+    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,
@@ -511,8 +588,26 @@
             // 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(STAGE_TYPE_UNDEFINED,
+            mSplitHandler.prepareDismissAnimation(topStageToKeep,
                     EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
                     finishTransaction);
 
@@ -790,6 +885,10 @@
                 } 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) {
@@ -830,6 +929,9 @@
         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) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index de03f58..9cd318f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,7 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -515,7 +516,7 @@
         }
 
         if (backgroundColorForTransition != 0) {
-            addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
+            addBackgroundColor(info, backgroundColorForTransition, startTransaction,
                     finishTransaction);
         }
 
@@ -546,7 +547,7 @@
         return true;
     }
 
-    private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
+    private void addBackgroundColor(@NonNull TransitionInfo info,
             @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction) {
         final Color bgColor = Color.valueOf(color);
@@ -558,9 +559,19 @@
                     .setName("animation-background")
                     .setCallsite("DefaultTransitionHandler")
                     .setColorLayer();
-
-            mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
             final SurfaceControl backgroundSurface = colorLayerBuilder.build();
+
+            // Attaching the background surface to the transition root could unexpectedly make it
+            // cover one of the split root tasks. To avoid this, put the background surface just
+            // above the display area when split is on.
+            final boolean isSplitTaskInvolved =
+                    info.getChanges().stream().anyMatch(c-> c.getTaskInfo() != null
+                            && c.getTaskInfo().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW);
+            if (isSplitTaskInvolved) {
+                mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
+            } else {
+                startTransaction.reparent(backgroundSurface, info.getRootLeash());
+            }
             startTransaction.setColor(backgroundSurface, colorArray)
                     .setLayer(backgroundSurface, -1)
                     .show(backgroundSurface);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
new file mode 100644
index 0000000..f561aa5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving the home
+ * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ */
+public class HomeTransitionObserver implements TransitionObserver,
+        RemoteCallable<HomeTransitionObserver> {
+    private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+            mListener;
+
+    private @NonNull final Context mContext;
+    private @NonNull final ShellExecutor mMainExecutor;
+    private @NonNull final Transitions mTransitions;
+
+    public HomeTransitionObserver(@NonNull Context context,
+            @NonNull ShellExecutor mainExecutor,
+            @NonNull Transitions transitions) {
+        mContext = context;
+        mMainExecutor = mainExecutor;
+        mTransitions = transitions;
+
+        mListener = new SingleInstanceRemoteListener<>(this,
+                c -> mTransitions.registerObserver(this),
+                c -> mTransitions.unregisterObserver(this));
+
+    }
+
+    @Override
+    public void onTransitionReady(@NonNull IBinder transition,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        for (TransitionInfo.Change change : info.getChanges()) {
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo == null || taskInfo.taskId == -1) {
+                continue;
+            }
+
+            final int mode = change.getMode();
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
+                    && TransitionUtil.isOpenOrCloseMode(mode)) {
+                mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)));
+            }
+        }
+    }
+
+    @Override
+    public void onTransitionStarting(@NonNull IBinder transition) {}
+
+    @Override
+    public void onTransitionMerged(@NonNull IBinder merged,
+            @NonNull IBinder playing) {}
+
+    @Override
+    public void onTransitionFinished(@NonNull IBinder transition,
+            boolean aborted) {}
+
+    /**
+     * Sets the home transition listener that receives any transitions resulting in a change of
+     *
+     */
+    public void setHomeTransitionListener(IHomeTransitionListener listener) {
+        if (listener != null) {
+            mListener.register(listener);
+        } else {
+            mListener.unregister();
+        }
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
+    }
+
+    /**
+     * Invalidates this controller, preventing future calls to send updates.
+     */
+    public void invalidate() {
+        mTransitions.unregisterObserver(this);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
new file mode 100644
index 0000000..18716c6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
+
+/**
+ *  Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ */
+interface IHomeTransitionListener {
+
+    /**
+     * Called when a transition changes the visibility of the home activity.
+     */
+    void onHomeVisibilityChanged(in boolean isVisible);
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index cc4d268..644a6a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -19,6 +19,8 @@
 import android.window.RemoteTransition;
 import android.window.TransitionFilter;
 
+import com.android.wm.shell.transition.IHomeTransitionListener;
+
 /**
  * Interface that is exposed to remote callers to manipulate the transitions feature.
  */
@@ -39,4 +41,7 @@
      * Retrieves the apply-token used by transactions in Shell
      */
     IBinder getShellApplyToken() = 3;
+
+    /** Set listener that will receive callbacks about transitions involving home activity */
+    oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index fab2dd2..b1fc16d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -63,7 +63,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (mTransition != transition) return false;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
-                + " transition %s for #%d.", mRemote, info.getDebugId());
+                + " transition %s for (#%d).", mRemote, info.getDebugId());
 
         final IBinder.DeathRecipient remoteDied = () -> {
             Log.e(Transitions.TAG, "Remote transition died, finishing");
@@ -152,6 +152,16 @@
     }
 
     @Override
+    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishTransaction) {
+        try {
+            mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted);
+        } catch (RemoteException e) {
+            Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e);
+        }
+    }
+
+    @Override
     public String toString() {
         return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
                 + mRemote.getRemoteTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index a90edf2..293b660 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -86,7 +86,16 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {
-        mRequestedRemotes.remove(transition);
+        RemoteTransition remoteTransition = mRequestedRemotes.remove(transition);
+        if (remoteTransition == null) {
+            return;
+        }
+
+        try {
+            remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error delegating onTransitionConsumed()", e);
+        }
     }
 
     @Override
@@ -117,7 +126,7 @@
                 }
             }
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
                 info.getDebugId(), pendingRemote);
 
         if (pendingRemote == null) return false;
@@ -137,13 +146,13 @@
                 });
             }
         };
+        // If the remote is actually in the same process, then make a copy of parameters since
+        // remote impls assume that they have to clean-up native references.
+        final SurfaceControl.Transaction remoteStartT =
+                copyIfLocal(startTransaction, remote.getRemoteTransition());
+        final TransitionInfo remoteInfo =
+                remoteStartT == startTransaction ? info : info.localRemoteCopy();
         try {
-            // If the remote is actually in the same process, then make a copy of parameters since
-            // remote impls assume that they have to clean-up native references.
-            final SurfaceControl.Transaction remoteStartT =
-                    copyIfLocal(startTransaction, remote.getRemoteTransition());
-            final TransitionInfo remoteInfo =
-                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
             handleDeath(remote.asBinder(), finishCallback);
             remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
@@ -151,6 +160,10 @@
             Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error running remote transition.", e);
+            if (remoteStartT != startTransaction) {
+                remoteStartT.close();
+            }
+            startTransaction.apply();
             unhandleDeath(remote.asBinder(), finishCallback);
             mRequestedRemotes.remove(transition);
             mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
@@ -232,7 +245,7 @@
         if (remote == null) return null;
         mRequestedRemotes.put(transition, remote);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
-                + " for %s: %s", transition, remote);
+                + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
         return new WindowContainerTransaction();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 87c438a..ba0ef20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,13 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
-import java.util.ArrayList;
-
 /**
  * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
  * as sentinels for fast-forwarding through animations when the screen is off.
@@ -34,30 +33,25 @@
  * don't register it like a normal handler.
  */
 class SleepHandler implements Transitions.TransitionHandler {
-    final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
-
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        mSleepTransitions.remove(transition);
-        startTransaction.apply();
-        finishCallback.onTransitionFinished(null);
-        return true;
+        if (info.hasChangesOrSideEffects()) {
+            Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition");
+            return false;
+        } else {
+            startTransaction.apply();
+            finishCallback.onTransitionFinished(null);
+            return true;
+        }
     }
 
     @Override
     @Nullable
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        mSleepTransitions.add(transition);
         return new WindowContainerTransaction();
     }
-
-    @Override
-    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
-            @Nullable SurfaceControl.Transaction finishTransaction) {
-        mSleepTransitions.remove(transition);
-    }
 }
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 c74b3f3..baa9aca 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
@@ -179,6 +179,7 @@
     private final DefaultTransitionHandler mDefaultTransitionHandler;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
     private final DisplayController mDisplayController;
+    private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
     private final SleepHandler mSleepHandler = new SleepHandler();
@@ -188,9 +189,6 @@
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
 
-    @Nullable
-    private final ShellCommandHandler mShellCommandHandler;
-
     private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
 
     /** List of {@link Runnable} instances to run when the last active transition has finished.  */
@@ -237,7 +235,7 @@
         @Override
         public String toString() {
             if (mInfo != null && mInfo.getDebugId() >= 0) {
-                return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
+                return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack();
             }
             return mToken.toString() + "@" + getTrack();
         }
@@ -275,13 +273,14 @@
             @NonNull ShellExecutor mainExecutor,
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor) {
-        this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
-                mainHandler, animExecutor, null,
-                new RootTaskDisplayAreaOrganizer(mainExecutor, context));
+        this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
+                displayController, mainExecutor, mainHandler, animExecutor,
+                new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
     }
 
     public Transitions(@NonNull Context context,
             @NonNull ShellInit shellInit,
+            @Nullable ShellCommandHandler shellCommandHandler,
             @NonNull ShellController shellController,
             @NonNull WindowOrganizer organizer,
             @NonNull TransactionPool pool,
@@ -289,7 +288,6 @@
             @NonNull ShellExecutor mainExecutor,
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor,
-            @Nullable ShellCommandHandler shellCommandHandler,
             @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
         mOrganizer = organizer;
         mContext = context;
@@ -300,13 +298,13 @@
         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
                 displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+        mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(mDefaultTransitionHandler);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
         // Next lowest priority is remote transitions.
         mHandlers.add(mRemoteTransitionHandler);
-        mShellCommandHandler = shellCommandHandler;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -339,9 +337,8 @@
             TransitionMetrics.getInstance();
         }
 
-        if (mShellCommandHandler != null) {
-            mShellCommandHandler.addCommandCallback("transitions", this, this);
-        }
+        mShellCommandHandler.addCommandCallback("transitions", this, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     public boolean isRegistered() {
@@ -359,7 +356,7 @@
     }
 
     private ExternalInterfaceBinder createExternalInterface() {
-        return new IShellTransitionsImpl(this);
+        return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
     }
 
     @Override
@@ -655,8 +652,8 @@
     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
-                transitionToken, info);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+                info.getDebugId(), transitionToken, info);
         final int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
             throw new IllegalStateException("Got transitionReady for non-pending transition "
@@ -1073,8 +1070,8 @@
 
     void requestStartTransition(@NonNull IBinder transitionToken,
             @Nullable TransitionRequestInfo request) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
-                transitionToken, request);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+                request.getDebugId(), transitionToken, request);
         if (isTransitionKnown(transitionToken)) {
             throw new RuntimeException("Transition already started " + transitionToken);
         }
@@ -1403,9 +1400,12 @@
     private static class IShellTransitionsImpl extends IShellTransitions.Stub
             implements ExternalInterfaceBinder {
         private Transitions mTransitions;
+        private final HomeTransitionObserver mHomeTransitionObserver;
 
-        IShellTransitionsImpl(Transitions transitions) {
+        IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) {
             mTransitions = transitions;
+            mHomeTransitionObserver = new HomeTransitionObserver(context, executor,
+                    mTransitions);
         }
 
         /**
@@ -1413,6 +1413,7 @@
          */
         @Override
         public void invalidate() {
+            mHomeTransitionObserver.invalidate();
             mTransitions = null;
         }
 
@@ -1437,6 +1438,14 @@
         public IBinder getShellApplyToken() {
             return SurfaceControl.Transaction.getDefaultApplyToken();
         }
+
+        @Override
+        public void setHomeTransitionListener(IHomeTransitionListener listener) {
+            executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
+                    (transitions) -> {
+                        mHomeTransitionObserver.setHomeTransitionListener(listener);
+                    });
+        }
     }
 
     private class SettingsObserver extends ContentObserver {
@@ -1475,4 +1484,68 @@
         pw.println(prefix + "tracing");
         mTracer.printShellCommandHelp(pw, prefix + "  ");
     }
+
+    private void dump(@NonNull PrintWriter pw, String prefix) {
+        pw.println(prefix + TAG);
+
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + "Handlers:");
+        for (TransitionHandler handler : mHandlers) {
+            pw.print(innerPrefix);
+            pw.print(handler.getClass().getSimpleName());
+            pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
+        }
+
+        pw.println(prefix + "Observers:");
+        for (TransitionObserver observer : mObservers) {
+            pw.print(innerPrefix);
+            pw.println(observer.getClass().getSimpleName());
+        }
+
+        pw.println(prefix + "Pending Transitions:");
+        for (ActiveTransition transition : mPendingTransitions) {
+            pw.print(innerPrefix + "token=");
+            pw.println(transition.mToken);
+            pw.print(innerPrefix + "id=");
+            pw.println(transition.mInfo != null
+                    ? transition.mInfo.getDebugId()
+                    : -1);
+            pw.print(innerPrefix + "handler=");
+            pw.println(transition.mHandler != null
+                    ? transition.mHandler.getClass().getSimpleName()
+                    : null);
+        }
+        if (mPendingTransitions.isEmpty()) {
+            pw.println(innerPrefix + "none");
+        }
+
+        pw.println(prefix + "Ready-during-sync Transitions:");
+        for (ActiveTransition transition : mReadyDuringSync) {
+            pw.print(innerPrefix + "token=");
+            pw.println(transition.mToken);
+            pw.print(innerPrefix + "id=");
+            pw.println(transition.mInfo != null
+                    ? transition.mInfo.getDebugId()
+                    : -1);
+            pw.print(innerPrefix + "handler=");
+            pw.println(transition.mHandler != null
+                    ? transition.mHandler.getClass().getSimpleName()
+                    : null);
+        }
+        if (mReadyDuringSync.isEmpty()) {
+            pw.println(innerPrefix + "none");
+        }
+
+        pw.println(prefix + "Tracks:");
+        for (int i = 0; i < mTracks.size(); i++) {
+            final ActiveTransition transition = mTracks.get(i).mActiveTransition;
+            pw.println(innerPrefix + "Track #" + i);
+            pw.print(innerPrefix + "active=");
+            pw.println(transition);
+            if (transition != null) {
+                pw.print(innerPrefix + "hander=");
+                pw.println(transition.mHandler);
+            }
+        }
+    }
 }
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 82fc0f4..aff35a3 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
@@ -231,4 +231,9 @@
     int getCaptionHeightId(@WindowingMode int windowingMode) {
         return R.dimen.freeform_decor_caption_height;
     }
+
+    @Override
+    int getCaptionViewId() {
+        return R.id.caption;
+    }
 }
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 bf99ab3..ca91d58 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
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -50,6 +51,8 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -67,6 +70,7 @@
 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.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -131,6 +135,8 @@
     private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
             new DesktopModeKeyguardChangeListener();
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private final DisplayInsetsController mDisplayInsetsController;
+    private boolean mInImmersiveMode;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -141,6 +147,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
+            DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
@@ -156,6 +163,7 @@
                 taskOrganizer,
                 displayController,
                 shellController,
+                displayInsetsController,
                 syncQueue,
                 transitions,
                 desktopTasksController,
@@ -176,6 +184,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
+            DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
@@ -191,6 +200,7 @@
         mTaskOrganizer = taskOrganizer;
         mShellController = shellController;
         mDisplayController = displayController;
+        mDisplayInsetsController = displayInsetsController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mDesktopTasksController = desktopTasksController;
@@ -213,6 +223,8 @@
             }
         });
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+                new DesktopModeOnInsetsChangedListener());
     }
 
     @Override
@@ -655,9 +667,9 @@
     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
         final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
         if (DesktopModeStatus.isEnabled()) {
-            if (relevantDecor == null
+            if (!mInImmersiveMode && (relevantDecor == null
                     || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
-                    || mTransitionDragActive) {
+                    || mTransitionDragActive)) {
                 handleCaptionThroughStatusBar(ev, relevantDecor);
             }
         }
@@ -1051,6 +1063,35 @@
             return mIsKeyguardVisible && mIsKeyguardOccluded;
         }
     }
+
+    @VisibleForTesting
+    class DesktopModeOnInsetsChangedListener implements
+            DisplayInsetsController.OnInsetsChangedListener {
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            for (int i = 0; i < insetsState.sourceSize(); i++) {
+                final InsetsSource source = insetsState.sourceAt(i);
+                if (source.getType() != statusBars()) {
+                    continue;
+                }
+
+                final DesktopModeWindowDecoration decor = getFocusedDecor();
+                if (decor == null) {
+                    return;
+                }
+                // If status bar inset is visible, top task is not in immersive mode
+                final boolean inImmersiveMode = !source.isVisible();
+                // Calls WindowDecoration#relayout if decoration visibility needs to be updated
+                if (inImmersiveMode != mInImmersiveMode) {
+                    decor.relayout(decor.mTaskInfo);
+                    mInImmersiveMode = inImmersiveMode;
+                }
+
+                return;
+            }
+        }
+    }
+
 }
 
 
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 380b59e..a976584 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
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.windowingModeToString;
 
+import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
@@ -27,6 +29,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -44,6 +47,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -93,7 +97,8 @@
 
     private ResizeVeil mResizeVeil;
 
-    private Drawable mAppIcon;
+    private Drawable mAppIconDrawable;
+    private Bitmap mAppIconBitmap;
     private CharSequence mAppName;
 
     private ExclusionRegionListener mExclusionRegionListener;
@@ -116,7 +121,8 @@
         this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
                 handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
-                WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+                WindowContainerTransaction::new, SurfaceControl::new,
+                new SurfaceControlViewHostFactory() {});
     }
 
     DesktopModeWindowDecoration(
@@ -133,10 +139,12 @@
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+            Supplier<SurfaceControl> surfaceControlSupplier,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
         super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
                 surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
-                windowContainerTransactionSupplier, surfaceControlViewHostFactory);
+                windowContainerTransactionSupplier, surfaceControlSupplier,
+                surfaceControlViewHostFactory);
 
         mHandler = handler;
         mChoreographer = choreographer;
@@ -252,7 +260,7 @@
                         mOnCaptionButtonClickListener,
                         mOnCaptionLongClickListener,
                         mAppName,
-                        mAppIcon
+                        mAppIconBitmap
                 );
             } else {
                 throw new IllegalArgumentException("Unexpected layout resource id");
@@ -359,10 +367,15 @@
         String packageName = mTaskInfo.realActivity.getPackageName();
         PackageManager pm = mContext.getApplicationContext().getPackageManager();
         try {
-            IconProvider provider = new IconProvider(mContext);
-            mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+            final IconProvider provider = new IconProvider(mContext);
+            mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
                     PackageManager.ComponentInfoFlags.of(0)));
-            ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+            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) {
@@ -383,7 +396,7 @@
      * until a resize event calls showResizeVeil below.
      */
     void createResizeVeil() {
-        mResizeVeil = new ResizeVeil(mContext, mAppIcon, mTaskInfo,
+        mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo,
                 mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
     }
 
@@ -452,26 +465,29 @@
     }
 
     /**
-     * Create and display handle menu window
+     * Create and display handle menu window.
      */
     void createHandleMenu() {
         mHandleMenu = new HandleMenu.Builder(this)
-                .setAppIcon(mAppIcon)
+                .setAppIcon(mAppIconBitmap)
                 .setAppName(mAppName)
                 .setOnClickListener(mOnCaptionButtonClickListener)
                 .setOnTouchListener(mOnCaptionTouchListener)
                 .setLayoutId(mRelayoutParams.mLayoutResId)
                 .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
                 .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
+                .setCaptionHeight(mResult.mCaptionHeight)
                 .build();
+        mWindowDecorViewHolder.onHandleMenuOpened();
         mHandleMenu.show();
     }
 
     /**
-     * Close the handle menu window
+     * Close the handle menu window.
      */
     void closeHandleMenu() {
         if (!isHandleMenuActive()) return;
+        mWindowDecorViewHolder.onHandleMenuClosed();
         mHandleMenu.close();
         mHandleMenu = null;
     }
@@ -638,6 +654,11 @@
         return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
     }
 
+    @Override
+    int getCaptionViewId() {
+        return R.id.desktop_mode_caption;
+    }
+
     /**
      * Add transition to mTransitionsPausingRelayout
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 389db62..dadd264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -18,6 +18,7 @@
 
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
@@ -45,6 +46,7 @@
     private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
     private int mCtrlType;
+    @Surface.Rotation private int mRotation;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
             DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
@@ -78,7 +80,10 @@
             mTaskOrganizer.applyTransaction(wct);
         }
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
-        if (mStableBounds.isEmpty()) {
+        int rotation = mWindowDecoration
+                .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+        if (mStableBounds.isEmpty() || mRotation != rotation) {
+            mRotation = rotation;
             mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
                     .getStableBounds(mStableBounds);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index 15f8f1c..1941d66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -29,9 +29,9 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.PointF;
-import android.graphics.drawable.Drawable;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -58,7 +58,7 @@
     private WindowDecoration.AdditionalWindow mHandleMenuWindow;
     private final PointF mHandleMenuPosition = new PointF();
     private final boolean mShouldShowWindowingPill;
-    private final Drawable mAppIcon;
+    private final Bitmap mAppIconBitmap;
     private final CharSequence mAppName;
     private final View.OnClickListener mOnClickListener;
     private final View.OnTouchListener mOnTouchListener;
@@ -71,10 +71,13 @@
     private int mMenuHeight;
     private int mMenuWidth;
 
+    private final int mCaptionHeight;
+
 
     HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
             View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
-            Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
+            Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
+            int captionHeight) {
         mParentDecor = parentDecor;
         mContext = mParentDecor.mDecorWindowContext;
         mTaskInfo = mParentDecor.mTaskInfo;
@@ -83,9 +86,10 @@
         mCaptionY = captionY;
         mOnClickListener = onClickListener;
         mOnTouchListener = onTouchListener;
-        mAppIcon = appIcon;
+        mAppIconBitmap = appIcon;
         mAppName = appName;
         mShouldShowWindowingPill = shouldShowWindowingPill;
+        mCaptionHeight = captionHeight;
         loadHandleMenuDimensions();
         updateHandleMenuPillPositions();
     }
@@ -98,6 +102,7 @@
         ssg.addTransaction(t);
         ssg.markSyncReady();
         setupHandleMenu();
+        animateHandleMenu();
     }
 
     private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
@@ -109,6 +114,21 @@
     }
 
     /**
+     * Animates the appearance of the handle menu and its three pills.
+     */
+    private void animateHandleMenu() {
+        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
+        final HandleMenuAnimator handleMenuAnimator = new HandleMenuAnimator(handleMenuView,
+                mMenuWidth, mCaptionHeight);
+        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+            handleMenuAnimator.animateCaptionHandleExpandToOpen();
+        } else {
+            handleMenuAnimator.animateOpen();
+        }
+    }
+
+    /**
      * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
      * pill.
      */
@@ -130,7 +150,7 @@
         final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
         final TextView appName = handleMenu.findViewById(R.id.application_name);
         collapseBtn.setOnClickListener(mOnClickListener);
-        appIcon.setImageDrawable(mAppIcon);
+        appIcon.setImageBitmap(mAppIconBitmap);
         appName.setText(mAppName);
     }
 
@@ -315,13 +335,14 @@
     static final class Builder {
         private final WindowDecoration mParent;
         private CharSequence mName;
-        private Drawable mAppIcon;
+        private Bitmap mAppIcon;
         private View.OnClickListener mOnClickListener;
         private View.OnTouchListener mOnTouchListener;
         private int mLayoutId;
         private int mCaptionX;
         private int mCaptionY;
         private boolean mShowWindowingPill;
+        private int mCaptionHeight;
 
 
         Builder(@NonNull WindowDecoration parent) {
@@ -333,7 +354,7 @@
             return this;
         }
 
-        Builder setAppIcon(@Nullable Drawable appIcon) {
+        Builder setAppIcon(@Nullable Bitmap appIcon) {
             mAppIcon = appIcon;
             return this;
         }
@@ -364,9 +385,14 @@
             return this;
         }
 
+        Builder setCaptionHeight(int captionHeight) {
+            mCaptionHeight = captionHeight;
+            return this;
+        }
+
         HandleMenu build() {
             return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
-                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
+                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
new file mode 100644
index 0000000..531de1f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.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.wm.shell.windowdecor
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.View.ALPHA
+import android.view.View.SCALE_X
+import android.view.View.SCALE_Y
+import android.view.View.TRANSLATION_Y
+import android.view.View.TRANSLATION_Z
+import android.view.ViewGroup
+import androidx.core.view.children
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
+
+/** Animates the Handle Menu opening. */
+class HandleMenuAnimator(
+    private val handleMenu: View,
+    private val menuWidth: Int,
+    private val captionHeight: Float
+) {
+    companion object {
+        private const val MENU_Y_TRANSLATION_DURATION: Long = 150
+        private const val HEADER_NONFREEFORM_SCALE_DURATION: Long = 150
+        private const val HEADER_FREEFORM_SCALE_DURATION: Long = 217
+        private const val HEADER_ELEVATION_DURATION: Long = 83
+        private const val HEADER_CONTENT_ALPHA_DURATION: Long = 100
+        private const val BODY_SCALE_DURATION: Long = 180
+        private const val BODY_ALPHA_DURATION: Long = 150
+        private const val BODY_ELEVATION_DURATION: Long = 83
+        private const val BODY_CONTENT_ALPHA_DURATION: Long = 167
+
+        private const val ELEVATION_DELAY: Long = 33
+        private const val HEADER_CONTENT_ALPHA_DELAY: Long = 67
+        private const val BODY_SCALE_DELAY: Long = 50
+        private const val BODY_ALPHA_DELAY: Long = 133
+
+        private const val HALF_INITIAL_SCALE: Float = 0.5f
+        private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f
+        private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f
+    }
+
+    private val animators: MutableList<Animator> = mutableListOf()
+
+    private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
+    private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
+    private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+
+    /** Animates the opening of the handle menu. */
+    fun animateOpen() {
+        prepareMenuForAnimation()
+        appInfoPillExpand()
+        animateAppInfoPill()
+        animateWindowingPill()
+        animateMoreActionsPill()
+        runAnimations()
+    }
+
+    /**
+     * Animates the opening of the handle menu. The caption handle in full screen and split screen
+     * will expand until it assumes the shape of the app info pill. Then, the other two pills will
+     * appear.
+     */
+    fun animateCaptionHandleExpandToOpen() {
+        prepareMenuForAnimation()
+        captionHandleExpandIntoAppInfoPill()
+        animateAppInfoPill()
+        animateWindowingPill()
+        animateMoreActionsPill()
+        runAnimations()
+    }
+
+    /**
+     * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
+     * Presets pivots of handle menu and body pills for scaling animation.
+     */
+    private fun prepareMenuForAnimation() {
+        // Preset opacity
+        appInfoPill.children.forEach { it.alpha = 0f }
+        windowingPill.alpha = 0f
+        moreActionsPill.alpha = 0f
+
+        // Setup pivots.
+        handleMenu.pivotX = menuWidth / 2f
+        handleMenu.pivotY = 0f
+
+        windowingPill.pivotX = menuWidth / 2f
+        windowingPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+        moreActionsPill.pivotX = menuWidth / 2f
+        moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+    }
+
+    private fun animateAppInfoPill() {
+        // Header Elevation Animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
+                startDelay = ELEVATION_DELAY
+                duration = HEADER_ELEVATION_DURATION
+            }
+
+        // Content Opacity Animation
+        appInfoPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+                    startDelay = HEADER_CONTENT_ALPHA_DELAY
+                    duration = HEADER_CONTENT_ALPHA_DURATION
+                }
+        }
+    }
+
+    private fun captionHandleExpandIntoAppInfoPill() {
+        // Header scaling animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
+                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
+                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+
+        // Downward y-translation animation
+        val yStart: Float = -captionHeight / 2
+        animators +=
+            ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
+                duration = MENU_Y_TRANSLATION_DURATION
+            }
+    }
+
+    private fun appInfoPillExpand() {
+        // Header scaling animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+                duration = HEADER_FREEFORM_SCALE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+                duration = HEADER_FREEFORM_SCALE_DURATION
+            }
+    }
+
+    private fun animateWindowingPill() {
+        // Windowing X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        // Windowing Opacity Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
+                startDelay = BODY_ALPHA_DELAY
+                duration = BODY_ALPHA_DURATION
+            }
+
+        // Windowing Elevation Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
+                startDelay = ELEVATION_DELAY
+                duration = BODY_ELEVATION_DURATION
+            }
+
+        // Windowing Content Opacity Animation
+        windowingPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+                    startDelay = BODY_ALPHA_DELAY
+                    duration = BODY_CONTENT_ALPHA_DURATION
+                    interpolator = Interpolators.FAST_OUT_SLOW_IN
+                }
+        }
+    }
+
+    private fun animateMoreActionsPill() {
+        // More Actions X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        // More Actions Opacity Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
+                startDelay = BODY_ALPHA_DELAY
+                duration = BODY_ALPHA_DURATION
+            }
+
+        // More Actions Elevation Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
+                startDelay = ELEVATION_DELAY
+                duration = BODY_ELEVATION_DURATION
+            }
+
+        // More Actions Content Opacity Animation
+        moreActionsPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+                    startDelay = BODY_ALPHA_DELAY
+                    duration = BODY_CONTENT_ALPHA_DURATION
+                    interpolator = Interpolators.FAST_OUT_SLOW_IN
+                }
+        }
+    }
+
+    /** Runs the list of animators concurrently. */
+    private fun runAnimations() {
+        val animatorSet = AnimatorSet()
+        animatorSet.playTogether(animators)
+        animatorSet.start()
+        animators.clear()
+    }
+}
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 09fc3da..368231e 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
@@ -132,6 +132,13 @@
                 t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction());
                 t.apply();
             });
+            mVeilAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    t.setAlpha(mVeilSurface, 1);
+                    t.apply();
+                }
+            });
 
             final ValueAnimator iconAnimator = new ValueAnimator();
             iconAnimator.setFloatValues(0f, 1f);
@@ -192,8 +199,8 @@
      */
     public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
         if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
-            // TODO(b/300145351): Investigate why ValueAnimator#end does not work here.
-            mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION);
+            mVeilAnimator.removeAllUpdateListeners();
+            mVeilAnimator.end();
         }
         relayout(newBounds, t);
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 303954a..852c037 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
@@ -21,6 +21,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -58,6 +59,7 @@
     private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
+    @Surface.Rotation private int mRotation;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
@@ -98,7 +100,10 @@
         }
         mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
-        if (mStableBounds.isEmpty()) {
+        int rotation = mDesktopWindowDecoration
+                .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+        if (mStableBounds.isEmpty() || mRotation != rotation) {
+            mRotation = rotation;
             mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
                     .getStableBounds(mStableBounds);
         }
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 335a588..634b755 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
@@ -18,7 +18,9 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.statusBars;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
@@ -30,6 +32,8 @@
 import android.graphics.Rect;
 import android.os.Binder;
 import android.view.Display;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
@@ -119,6 +123,7 @@
     private WindowlessWindowManager mCaptionWindowManager;
     private SurfaceControlViewHost mViewHost;
     private Configuration mWindowDecorConfig;
+    private boolean mIsCaptionVisible;
 
     private final Binder mOwner = new Binder();
     private final Rect mCaptionInsetsRect = new Rect();
@@ -133,7 +138,8 @@
             Configuration windowDecorConfig) {
         this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
-                WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+                WindowContainerTransaction::new, SurfaceControl::new,
+                new SurfaceControlViewHostFactory() {});
     }
 
     WindowDecoration(
@@ -141,17 +147,18 @@
             DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
             RunningTaskInfo taskInfo,
-            SurfaceControl taskSurface,
+            @NonNull SurfaceControl taskSurface,
             Configuration windowDecorConfig,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+            Supplier<SurfaceControl> surfaceControlSupplier,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
         mContext = context;
         mDisplayController = displayController;
         mTaskOrganizer = taskOrganizer;
         mTaskInfo = taskInfo;
-        mTaskSurface = taskSurface;
+        mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
         mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
@@ -225,6 +232,8 @@
                     .inflate(params.mLayoutResId, null);
         }
 
+        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+
         final Resources resources = mDecorWindowContext.getResources();
         final Configuration taskConfig = mTaskInfo.getConfiguration();
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
@@ -260,10 +269,10 @@
                     .build();
         }
 
-        final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+        outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
 
-        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
 
@@ -272,18 +281,26 @@
 
             // Caption insets
             mCaptionInsetsRect.set(taskBounds);
-            mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
-            wct.addInsetsSource(mTaskInfo.token,
-                    mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
-            wct.addInsetsSource(mTaskInfo.token,
-                    mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
-                    mCaptionInsetsRect);
+            if (mIsCaptionVisible) {
+                mCaptionInsetsRect.bottom =
+                        mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
+                wct.addInsetsSource(mTaskInfo.token,
+                        mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+                wct.addInsetsSource(mTaskInfo.token,
+                        mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+                        mCaptionInsetsRect);
+            } else {
+                wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+                        WindowInsets.Type.captionBar());
+                wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+                        WindowInsets.Type.mandatorySystemGestures());
+            }
         } else {
             startT.hide(mCaptionContainerSurface);
         }
 
         // Task surface itself
-        float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+        float shadowRadius;
         final Point taskPosition = mTaskInfo.positionInParent;
         if (isFullscreen) {
             // Setting the task crop to the width/height stops input events from being sent to
@@ -294,9 +311,12 @@
             // drag-resized by the window decoration.
             startT.setWindowCrop(mTaskSurface, null);
             finishT.setWindowCrop(mTaskSurface, null);
+            // Shadow is not needed for fullscreen tasks
+            shadowRadius = 0;
         } else {
             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+            shadowRadius = loadDimension(resources, params.mShadowRadiusId);
         }
         startT.setShadowRadius(mTaskSurface, shadowRadius)
                 .show(mTaskSurface);
@@ -328,7 +348,7 @@
         // Caption view
         mCaptionWindowManager.setConfiguration(taskConfig);
         final WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(captionWidth, captionHeight,
+                new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
                         WindowManager.LayoutParams.TYPE_APPLICATION,
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -348,10 +368,41 @@
         }
     }
 
+    /**
+     * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
+     */
+    private void updateCaptionVisibility(View rootView, int displayId) {
+        final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
+        for (int i = 0; i < insetsState.sourceSize(); i++) {
+            final InsetsSource source = insetsState.sourceAt(i);
+            if (source.getType() != statusBars()) {
+                continue;
+            }
+
+            mIsCaptionVisible = source.isVisible();
+            setCaptionVisibility(rootView, mIsCaptionVisible);
+
+            return;
+        }
+    }
+
+    private void setCaptionVisibility(View rootView, boolean visible) {
+        if (rootView == null) {
+            return;
+        }
+        final int v = visible ? View.VISIBLE : View.GONE;
+        final View captionView = rootView.findViewById(getCaptionViewId());
+        captionView.setVisibility(v);
+    }
+
     int getCaptionHeightId(@WindowingMode int windowingMode) {
         return Resources.ID_NULL;
     }
 
+    int getCaptionViewId() {
+        return Resources.ID_NULL;
+    }
+
     /**
      * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
      * registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -405,6 +456,7 @@
     public void close() {
         mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
         releaseViews();
+        mTaskSurface.release();
     }
 
     static int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -421,6 +473,13 @@
         return resources.getDimension(resourceId);
     }
 
+    private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
+            Supplier<SurfaceControl> surfaceControlSupplier) {
+        final SurfaceControl copy = surfaceControlSupplier.get();
+        copy.copyFrom(sc, "WindowDecoration");
+        return copy;
+    }
+
     /**
      * Create a window associated with this WindowDecoration.
      * Note that subclass must dispose of this when the task is hidden/closed.
@@ -466,7 +525,8 @@
      */
     public void addCaptionInset(WindowContainerTransaction wct) {
         final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode());
-        if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+        if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL
+                || !mIsCaptionVisible) {
             return;
         }
 
@@ -509,6 +569,7 @@
     }
 
     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+        int mCaptionHeight;
         int mWidth;
         int mHeight;
         T mRootView;
@@ -516,6 +577,7 @@
         void reset() {
             mWidth = 0;
             mHeight = 0;
+            mCaptionHeight = 0;
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 6b59cce..d64312a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -2,7 +2,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.content.res.ColorStateList
-import android.graphics.drawable.Drawable
+import android.graphics.Bitmap
 import android.graphics.drawable.GradientDrawable
 import android.view.View
 import android.view.View.OnLongClickListener
@@ -22,7 +22,7 @@
         onCaptionButtonClickListener: View.OnClickListener,
         onLongClickListener: OnLongClickListener,
         appName: CharSequence,
-        appIcon: Drawable
+        appIconBitmap: Bitmap
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
@@ -44,11 +44,10 @@
         maximizeWindowButton.onLongClickListener = onLongClickListener
         closeWindowButton.setOnTouchListener(onCaptionTouchListener)
         appNameTextView.text = appName
-        appIconImageView.setImageDrawable(appIcon)
+        appIconImageView.setImageBitmap(appIconBitmap)
     }
 
     override fun bindData(taskInfo: RunningTaskInfo) {
-
         val captionDrawable = captionView.background as GradientDrawable
         taskInfo.taskDescription?.statusBarColor?.let {
             captionDrawable.setColor(it)
@@ -63,6 +62,10 @@
         appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
     }
 
+    override fun onHandleMenuOpened() {}
+
+    override fun onHandleMenuClosed() {}
+
     private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_app_name_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 9374ac9..9dc86db 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
@@ -1,11 +1,13 @@
 package com.android.wm.shell.windowdecor.viewholder
 
+import android.animation.ObjectAnimator
 import android.app.ActivityManager.RunningTaskInfo
 import android.content.res.ColorStateList
 import android.graphics.drawable.GradientDrawable
 import android.view.View
 import android.widget.ImageButton
 import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
 
 /**
  * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
@@ -17,6 +19,10 @@
         onCaptionButtonClickListener: View.OnClickListener
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
+    companion object {
+        private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
+    }
+
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
 
@@ -35,6 +41,14 @@
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
     }
 
+    override fun onHandleMenuOpened() {
+        animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
+    }
+
+    override fun onHandleMenuClosed() {
+        animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
+    }
+
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
@@ -42,4 +56,14 @@
             context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
         }
     }
+
+    /** Animate appearance/disappearance of caption handle as the handle menu is animated. */
+    private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
+        val animator =
+            ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
+                duration = CAPTION_HANDLE_ANIMATION_DURATION
+                interpolator = Interpolators.FAST_OUT_SLOW_IN
+            }
+        animator.start()
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 49e8d15..8b405f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -35,4 +35,10 @@
           }
         } ?: false
   }
+
+    /** Callback when the handle menu is opened. */
+    abstract fun onHandleMenuOpened()
+
+    /** Callback when the handle menu is closed. */
+    abstract fun onHandleMenuClosed()
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 0058d11..acfb259 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@
 }
 
 filegroup {
-    name: "WMShellFlickerTestsPip-src",
+    name: "WMShellFlickerTestsPip1-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/pip/A*.kt",
+        "src/com/android/wm/shell/flicker/pip/B*.kt",
+        "src/com/android/wm/shell/flicker/pip/C*.kt",
+        "src/com/android/wm/shell/flicker/pip/D*.kt",
+        "src/com/android/wm/shell/flicker/pip/S*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip2-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/pip/E*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip3-src",
     srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
 }
 
@@ -116,6 +134,7 @@
         "wm-flicker-common-assertions",
         "launcher-helper-lib",
         "launcher-aosp-tapl",
+        "com_android_wm_shell_flags_lib",
     ],
 }
 
@@ -175,7 +194,9 @@
     ],
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPip3-src",
         ":WMShellFlickerTestsPipCommon-src",
         ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -199,19 +220,49 @@
 }
 
 android_test {
-    name: "WMShellFlickerTestsPip",
+    name: "WMShellFlickerTestsPip1",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestPip.xml"],
     package_name: "com.android.wm.shell.flicker.pip",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip",
     srcs: [
         ":WMShellFlickerTestsBase-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip1-src",
         ":WMShellFlickerTestsPipCommon-src",
     ],
 }
 
 android_test {
+    name: "WMShellFlickerTestsPip2",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPip3",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip3-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+    ],
+}
+
+android_test {
     name: "WMShellFlickerTestsPipApps",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestPip.xml"],
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b13e9a2..1df1136 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -81,9 +81,7 @@
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6000s"/>
         <option name="hidden-api-checks" value="false"/>
-        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
         <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        -->
         <!-- PerfettoListener related arguments -->
         <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
         <option name="instrumentation-arg"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 19c8435..94e3959 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -22,6 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,7 +56,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
-    EnterPipViaAppUiButtonTest(flicker) {
+    EnterPipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 6748626..0fd1b2c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.subject.exceptions.IncorrectRegionException
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -40,14 +41,26 @@
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
 
-    /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+    /**
+     * Checks that the visible region area of [pipApp] decreases
+     * and then increases during the animation.
+     */
     @Presubmit
     @Test
-    fun pipLayerAreaDecreases() {
+    fun pipLayerAreaDecreasesThenIncreases() {
+        val isAreaDecreasing = arrayOf(true)
         flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
-                current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+                if (isAreaDecreasing[0]) {
+                    try {
+                        current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+                    } catch (e: IncorrectRegionException) {
+                        isAreaDecreasing[0] = false
+                    }
+                } else {
+                    previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
+                }
             }
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 2cd08a4a..5965805 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -26,6 +26,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -66,8 +67,10 @@
             standardAppHelper.launchViaIntent(
                 wmHelper,
                 NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
-                ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME,
-                    NetflixAppHelper.WATCH_ACTIVITY)
+                ComponentNameMatcher(
+                    NetflixAppHelper.PACKAGE_NAME,
+                    NetflixAppHelper.WATCH_ACTIVITY
+                )
             )
             standardAppHelper.waitForVideoPlaying()
         }
@@ -99,6 +102,41 @@
         super.focusChanges()
     }
 
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
+        flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+    }
+
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+    }
+
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+        flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+    }
+
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+        flicker.statusBarLayerPositionAtEnd()
+    }
+
+    @Postsubmit
+    @Test override fun statusBarWindowIsAlwaysVisible() {
+        // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index d09a90c..aadadd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -35,6 +35,7 @@
     static_libs: [
         "WindowManager-Shell",
         "junit",
+        "flag-junit-base",
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
@@ -49,6 +50,7 @@
         "testables",
         "platform-test-annotations",
         "servicestests-utils",
+        "com_android_wm_shell_flags_lib",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e7d0f60..d6141cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -568,8 +568,12 @@
     }
 
     private void registerAnimation(int type) {
-        mController.registerAnimation(type,
-                new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+        mController.registerAnimation(
+                type,
+                new BackAnimationRunner(
+                        mAnimatorCallback,
+                        mBackAnimationRunner,
+                        mContext));
     }
 
     private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index d34e27b..db98abb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.ComponentName;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -60,6 +61,9 @@
     /** The minimum possible size of the override min size's width or height */
     private static final int OVERRIDABLE_MIN_SIZE = 40;
 
+    /** The margin of error for floating point results. */
+    private static final float MARGIN_OF_ERROR = 0.05f;
+
     private PipBoundsState mPipBoundsState;
     private SizeSpecSource mSizeSpecSource;
     private ComponentName mTestComponentName1;
@@ -88,6 +92,27 @@
     }
 
     @Test
+    public void testBoundsScale() {
+        mPipBoundsState.setMaxSize(300, 300);
+        mPipBoundsState.setBounds(new Rect(0, 0, 100, 100));
+
+        final int currentWidth = mPipBoundsState.getBounds().width();
+        final Point maxSize = mPipBoundsState.getMaxSize();
+        final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f);
+
+        // test for currentWidth < maxWidth
+        assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR);
+
+        // reset the bounds to be at the maximum size spec
+        mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y));
+        assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+
+        // reset the bounds to be over the maximum size spec
+        mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2));
+        assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+    }
+
+    @Test
     public void testSetReentryState() {
         final Size size = new Size(100, 100);
         final float snapFraction = 0.5f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 6777a5b..9719ba8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -28,11 +28,13 @@
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableResources;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
@@ -98,6 +100,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        final TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(R.bool.config_pipEnablePinchResize, true);
+
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
         mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
         mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ebc284b..befc702 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -208,6 +208,30 @@
 
     @Test
     @UiThreadTest
+    public void testRemoteTransitionConsumed() {
+        // Omit side child change
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+                .addChange(TRANSIT_OPEN, mMainChild)
+                .build();
+        TestRemoteTransition testRemote = new TestRemoteTransition();
+
+        IBinder transition = mSplitScreenTransitions.startEnterTransition(
+                TRANSIT_OPEN, new WindowContainerTransaction(),
+                new RemoteTransition(testRemote, "Test"), mStageCoordinator,
+                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+        mMainStage.onTaskAppeared(mMainChild, createMockSurface());
+        boolean accepted = mStageCoordinator.startAnimation(transition, info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(Transitions.TransitionFinishCallback.class));
+        assertTrue(accepted);
+
+        assertTrue(testRemote.isConsumed());
+
+    }
+
+    @Test
+    @UiThreadTest
     public void testMonitorInSplit() {
         enterSplit();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index d598678..da83d4c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -50,6 +50,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
@@ -92,6 +93,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
@@ -145,7 +147,9 @@
         final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
                 mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
                 mMainHandler, mAnimExecutor);
-        verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+        // One from Transitions, one from RootTaskDisplayAreaOrganizer
+        verify(shellInit).addInitCallback(any(), eq(t));
+        verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
     }
 
     @Test
@@ -285,6 +289,10 @@
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+            }
         };
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
@@ -427,6 +435,10 @@
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+            }
         };
 
         TransitionFilter filter = new TransitionFilter();
@@ -473,6 +485,10 @@
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+            }
         };
 
         final int transitType = TRANSIT_FIRST_CUSTOM + 1;
@@ -1153,7 +1169,7 @@
     }
 
     @Test
-    public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+    public void testEmptyTransition_withKeyguardGoingAway_plays() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
@@ -1172,6 +1188,65 @@
     }
 
     @Test
+    public void testSleepTransition_withKeyguardGoingAway_plays(){
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a no-op transition
+        TransitionInfo info = new TransitionInfoBuilder(
+                TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If keyguard-going-away flag set, then it shouldn't be aborted.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+    @Test
+    public void testSleepTransition_withChanges_plays(){
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a transition with some changes
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP)
+                .addChange(TRANSIT_OPEN).build();
+        info.setTrack(0);
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If there is an actual change, then it shouldn't be aborted.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+
+    @Test
+    public void testSleepTransition_empty_SyncBySleepHandler() {
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a no-op transition
+        TransitionInfo info = new TransitionInfoBuilder(
+                TRANSIT_SLEEP, 0x0, true /* noOp */).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If there is nothing to actually play, it should not be offered to handlers.
+        assertEquals(0, mDefaultHandler.activeCount());
+    }
+
+    @Test
     public void testMultipleTracks() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
index 39ab238..87330d2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -31,6 +31,7 @@
  */
 public class TestRemoteTransition extends IRemoteTransition.Stub {
     private boolean mCalled = false;
+    private boolean mConsumed = false;
     final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
 
     @Override
@@ -48,6 +49,11 @@
             IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
     }
 
+    @Override
+    public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+        mConsumed = true;
+    }
+
     /**
      * Check whether this remote transition
      * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
@@ -56,4 +62,12 @@
     public boolean isCalled() {
         return mCalled;
     }
+
+    /**
+     * Check whether this remote transition's {@link #onTransitionConsumed(IBinder, boolean)}
+     * is called
+     */
+    public boolean isConsumed() {
+        return mConsumed;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 8eaf5a0..57aa47e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -33,8 +33,12 @@
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.InputChannel
 import android.view.InputMonitor
+import android.view.InsetsSource
+import android.view.InsetsState
 import android.view.SurfaceControl
 import android.view.SurfaceView
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
 import androidx.core.content.getSystemService
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -42,6 +46,7 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
@@ -53,10 +58,12 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -68,6 +75,7 @@
 import java.util.Optional
 import java.util.function.Supplier
 
+
 /** Tests of [DesktopModeWindowDecorViewModel]  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -80,6 +88,7 @@
     @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayController: DisplayController
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
+    @Mock private lateinit var displayInsetsController: DisplayInsetsController
     @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
     @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
     @Mock private lateinit var mockInputMonitor: InputMonitor
@@ -97,6 +106,7 @@
     }
 
     private lateinit var shellInit: ShellInit
+    private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
     private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
 
     @Before
@@ -111,6 +121,7 @@
                 mockTaskOrganizer,
                 mockDisplayController,
                 mockShellController,
+                displayInsetsController,
                 mockSyncQueue,
                 mockTransitions,
                 Optional.of(mockDesktopTasksController),
@@ -131,6 +142,11 @@
         whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1])
 
         shellInit.init()
+
+        val listenerCaptor =
+                argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+        verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
+        desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
     }
 
     @Test
@@ -274,6 +290,67 @@
         verify(decoration).addTransitionPausingRelayout(transition)
     }
 
+    @Test
+    fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task)
+
+        // Add status bar insets source
+        val insetsState = InsetsState()
+        val statusBarInsetsSourceId = 0
+        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+        statusBarInsetsSource.isVisible = false
+        insetsState.addSource(statusBarInsetsSource)
+
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+        // Verify relayout occurs when status bar inset visibility changes
+        verify(decoration, times(1)).relayout(task)
+    }
+
+    @Test
+    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task)
+
+        // Add navigation bar insets source
+        val insetsState = InsetsState()
+        val navigationBarInsetsSourceId = 1
+        val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
+        navigationBarInsetsSource.isVisible = false
+        insetsState.addSource(navigationBarInsetsSource)
+
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+        // Verify relayout does not occur when non-status bar inset changes visibility
+        verify(decoration, never()).relayout(task)
+    }
+
+    @Test
+    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task)
+
+        // Add status bar insets source
+        val insetsState = InsetsState()
+        val statusBarInsetsSourceId = 0
+        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+        statusBarInsetsSource.isVisible = false
+        insetsState.addSource(statusBarInsetsSource)
+
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+        // Verify relayout runs only once when status bar inset visibility changes.
+        verify(decoration, times(1)).relayout(task)
+    }
+
     private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
         desktopModeWindowDecorViewModel.onTaskOpening(
                 task,
@@ -313,6 +390,8 @@
         whenever(mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), eq(task), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
+        decoration.mTaskInfo = task
+        whenever(decoration.isFocused).thenReturn(task.isFocused)
         return decoration
     }
 
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 a2dbab1..18fcdd0 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
@@ -111,7 +111,8 @@
                 mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
                 mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
                 SurfaceControl.Builder::new, mMockTransactionSupplier,
-                WindowContainerTransaction::new, mMockSurfaceControlViewHostFactory);
+                WindowContainerTransaction::new, SurfaceControl::new,
+                mMockSurfaceControlViewHostFactory);
     }
 
     private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index c0c4498..add78b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -6,6 +6,9 @@
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.view.Display
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
 import android.view.SurfaceControl
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
@@ -24,6 +27,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.never
@@ -76,7 +80,15 @@
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
         whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
-            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+            if (mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_90 ||
+                mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_270
+            ) {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+            } else {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+            }
         }
         `when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
         `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
@@ -89,6 +101,7 @@
             defaultMinSize = DEFAULT_MIN
             displayId = DISPLAY_ID
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+            configuration.windowConfiguration.displayRotation = ROTATION_90
         }
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -623,7 +636,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat()
-        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -641,11 +654,83 @@
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS.top
+                        STABLE_BOUNDS_LANDSCAPE.top
             }
         })
     }
 
+    @Test
+    fun testDragResize_drag_updatesStableBoundsOnRotate() {
+        // Test landscape stable bounds
+        performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        val rectAfterDrag = Rect(STARTING_BOUNDS)
+        rectAfterDrag.right += 2000
+        // First drag; we should fetch stable bounds.
+        verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag
+            }
+        })
+        // Drag back to starting bounds.
+        performDrag(
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+        // Display did not rotate; we should use previous stable bounds
+        verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+
+        // Rotate the screen to portrait
+        mockWindowDecoration.mTaskInfo.apply {
+            configuration.windowConfiguration.displayRotation = Surface.ROTATION_0
+        }
+        // Test portrait stable bounds
+        performDrag(
+            STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        rectAfterDrag.right -= 2000
+        rectAfterDrag.bottom += 2000
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag
+            }
+        })
+        // Display has rotated; we expect a new stable bounds.
+        verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
+    }
+
+    private fun performDrag(
+        startX: Float,
+        startY: Float,
+        endX: Float,
+        endY: Float,
+        ctrlType: Int
+    ) {
+        taskPositioner.onDragPositioningStart(
+            ctrlType,
+            startX,
+            startY
+        )
+        taskPositioner.onDragPositioningMove(
+            endX,
+            endY
+        )
+
+        taskPositioner.onDragPositioningEnd(
+            endX,
+            endY
+        )
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -664,11 +749,17 @@
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom)
-        private val STABLE_BOUNDS = Rect(
+        private val STABLE_BOUNDS_LANDSCAPE = Rect(
                 DISPLAY_BOUNDS.left,
                 DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
+        private val STABLE_BOUNDS_PORTRAIT = Rect(
+            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+            DISPLAY_BOUNDS.bottom,
+            DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 8913453..a70ebf1 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
@@ -21,6 +21,9 @@
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.WindowContainerToken
@@ -30,6 +33,7 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -93,10 +97,17 @@
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
         whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
-            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+            if (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                .displayRotation == ROTATION_90 ||
+                mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_270
+            ) {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+            } else {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+            }
         }
         `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
-
         mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
             taskId = TASK_ID
             token = taskToken
@@ -105,6 +116,7 @@
             defaultMinSize = DEFAULT_MIN
             displayId = DISPLAY_ID
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+            configuration.windowConfiguration.displayRotation = ROTATION_90
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -343,7 +355,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat()
-        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -361,11 +373,79 @@
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS.top
+                        STABLE_BOUNDS_LANDSCAPE.top
             }
         })
     }
 
+    @Test
+    fun testDragResize_drag_updatesStableBoundsOnRotate() {
+        // Test landscape stable bounds
+        performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        val rectAfterDrag = Rect(STARTING_BOUNDS)
+        rectAfterDrag.right += 2000
+        // First drag; we should fetch stable bounds.
+        verify(mockDisplayLayout, times(1)).getStableBounds(any())
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+            eq(taskPositioner))
+        // Drag back to starting bounds.
+        performDrag(STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+        // Display did not rotate; we should use previous stable bounds
+        verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+        // Rotate the screen to portrait
+        mockDesktopWindowDecoration.mTaskInfo.apply {
+            configuration.windowConfiguration.displayRotation = ROTATION_0
+        }
+        // Test portrait stable bounds
+        performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        rectAfterDrag.right -= 2000
+        rectAfterDrag.bottom += 2000
+
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+            eq(taskPositioner))
+        // Display has rotated; we expect a new stable bounds.
+        verify(mockDisplayLayout, times(2)).getStableBounds(any())
+    }
+
+    private fun performDrag(
+        startX: Float,
+        startY: Float,
+        endX: Float,
+        endY: Float,
+        ctrlType: Int
+    ) {
+        taskPositioner.onDragPositioningStart(
+            ctrlType,
+            startX,
+            startY
+        )
+        taskPositioner.onDragPositioningMove(
+            endX,
+            endY
+        )
+
+        taskPositioner.onDragPositioningEnd(
+            endX,
+            endY
+        )
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -378,11 +458,17 @@
         private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
         private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
-        private val STABLE_BOUNDS = Rect(
+        private val STABLE_BOUNDS_LANDSCAPE = Rect(
             DISPLAY_BOUNDS.left,
             DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
             DISPLAY_BOUNDS.right,
             DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
+        private val STABLE_BOUNDS_PORTRAIT = Rect(
+            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+            DISPLAY_BOUNDS.bottom,
+            DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fcb7863..8e42f74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,6 +18,9 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
@@ -25,6 +28,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,6 +56,7 @@
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
 import android.view.Display;
+import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
@@ -94,6 +100,7 @@
     private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
     private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
     private static final int CORNER_RADIUS = 20;
+    private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
 
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
@@ -114,10 +121,13 @@
     private WindowContainerTransaction mMockWindowContainerTransaction;
     @Mock
     private SurfaceSyncGroup mMockSurfaceSyncGroup;
+    @Mock
+    private SurfaceControl mMockTaskSurface;
 
     private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
             new ArrayList<>();
     private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
+    private final InsetsState mInsetsState = new InsetsState();
     private SurfaceControl.Transaction mMockSurfaceControlStartT;
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
     private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
@@ -141,6 +151,11 @@
                 .create(any(), any(), any());
         when(mMockSurfaceControlViewHost.getRootSurfaceControl())
                 .thenReturn(mMockRootSurfaceControl);
+        when(mMockView.findViewById(anyInt())).thenReturn(mMockView);
+
+        // Add status bar inset so that WindowDecoration does not think task is in immersive mode
+        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
+        doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
     }
 
     @Test
@@ -176,8 +191,7 @@
         // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
 
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo);
 
@@ -186,7 +200,7 @@
         verify(captionContainerSurfaceBuilder, never()).build();
         verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
 
-        verify(mMockSurfaceControlFinishT).hide(taskSurface);
+        verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
 
         assertNull(mRelayoutResult.mRootView);
     }
@@ -216,12 +230,11 @@
         taskInfo.isFocused = true;
         // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo);
 
-        verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+        verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
         verify(decorContainerSurfaceBuilder).setContainerLayer();
         verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
         verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
@@ -249,14 +262,15 @@
         }
 
         verify(mMockSurfaceControlFinishT)
-                .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+                .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
+                        TASK_POSITION_IN_PARENT.y);
         verify(mMockSurfaceControlFinishT)
-                .setWindowCrop(taskSurface, 300, 100);
-        verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
-        verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
+                .setWindowCrop(mMockTaskSurface, 300, 100);
+        verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+        verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlStartT)
-                .show(taskSurface);
-        verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
+                .show(mMockTaskSurface);
+        verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
 
         assertEquals(300, mRelayoutResult.mWidth);
         assertEquals(100, mRelayoutResult.mHeight);
@@ -295,8 +309,7 @@
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
         mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
 
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo);
 
@@ -333,8 +346,7 @@
                 .setVisible(true)
                 .build();
 
-        final TestWindowDecoration windowDecor =
-                createWindowDecoration(taskInfo, new SurfaceControl());
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
         windowDecor.relayout(taskInfo);
 
         // It shouldn't show the window decoration when it can't obtain the display instance.
@@ -392,8 +404,7 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
         windowDecor.relayout(taskInfo);
 
         final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
@@ -452,8 +463,7 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo);
 
@@ -493,8 +503,7 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
 
@@ -532,17 +541,44 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM)
                 .build();
         taskInfo.isFocused = true;
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo);
 
-        verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
+        verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
 
         mockitoSession.finishMocking();
     }
 
     @Test
+    public void testInsetsAddedWhenCaptionIsVisible() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder();
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setVisible(true)
+                .build();
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+        assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
+                .isVisible());
+        assertTrue(mInsetsState.sourceSize() == 1);
+        assertTrue(mInsetsState.sourceAt(0).getType() == statusBars());
+
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(captionBar()), any());
+        verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(mandatorySystemGestures()), any());
+    }
+
+    @Test
     public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
                 DesktopModeStatus.class).strictness(LENIENT).startMocking();
@@ -571,25 +607,50 @@
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .build();
         taskInfo.isFocused = true;
-        final SurfaceControl taskSurface = mock(SurfaceControl.class);
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
         windowDecor.relayout(taskInfo);
 
-        verify(mMockSurfaceControlStartT).unsetColor(taskSurface);
+        verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
 
         mockitoSession.finishMocking();
     }
 
-    private TestWindowDecoration createWindowDecoration(
-            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+
+    @Test
+    public void testInsetsRemovedWhenCaptionIsHidden() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder();
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setVisible(true)
+                .build();
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(captionBar()));
+        verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(mandatorySystemGestures()));
+    }
+
+    private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
         return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
-                taskInfo, testSurface, mWindowConfiguration,
+                taskInfo, mMockTaskSurface, mWindowConfiguration,
                 new MockObjectSupplier<>(mMockSurfaceControlBuilders,
                         () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
                 new MockObjectSupplier<>(mMockSurfaceControlTransactions,
                         () -> mock(SurfaceControl.Transaction.class)),
-                () -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
+                () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
+                mMockSurfaceControlViewHostFactory);
     }
 
     private class MockObjectSupplier<T> implements Supplier<T> {
@@ -628,11 +689,12 @@
                 Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
                 Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
                 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+                Supplier<SurfaceControl> surfaceControlSupplier,
                 SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
             super(context, displayController, taskOrganizer, taskInfo, taskSurface,
                     windowConfiguration, surfaceControlBuilderSupplier,
                     surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
-                    surfaceControlViewHostFactory);
+                    surfaceControlSupplier, surfaceControlViewHostFactory);
         }
 
         @Override
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f22693..d056248 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@
       has_locale = true;
     }
 
-    // if we don't have a result yet
+      // if we don't have a result yet
     if (!final_result ||
         // or this config is better before the locale than the existing result
         result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
@@ -863,9 +863,12 @@
 
       // We can skip calling ResTable_config::match() if the caller does not care for the
       // configuration to match or if we're using the list of types that have already had their
-      // configuration matched.
+      // configuration matched. The exception to this is when the user has multiple locales set
+      // because the filtered list will then have values from multiple locales and we will need to
+      // call match() to make sure the current entry matches the config we are currently checking.
       const ResTable_config& this_config = type_entry->config;
-      if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+      if (!((use_filtered && (configurations_.size() == 1))
+          || ignore_configuration || this_config.match(desired_config))) {
         continue;
       }
 
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ffb329d..00d049c 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -33,6 +33,14 @@
 #endif  // __ANDROID__
 }
 
+inline bool deprecate_ui_fonts() {
+#ifdef __ANDROID__
+    return com_android_text_flags_deprecate_ui_fonts();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index d0d3c5e..e986c38 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -20,3 +20,17 @@
   description: "APIs to help enable animations involving gainmaps"
   bug: "296482289"
 }
+
+flag {
+  name: "gainmap_constructor_with_metadata"
+  namespace: "core_graphics"
+  description: "APIs to create a new gainmap with a bitmap for metadata."
+  bug: "304478551"
+}
+
+flag {
+  name: "clip_surfaceviews"
+  namespace: "core_graphics"
+  description: "Clip z-above surfaceviews to global clip rect"
+  bug: "298621623"
+}
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index bcfb4c8..7552b56d 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -16,12 +16,15 @@
 
 #include "MinikinUtils.h"
 
-#include <string>
-
 #include <log/log.h>
-
+#include <minikin/FamilyVariant.h>
 #include <minikin/MeasuredText.h>
 #include <minikin/Measurement.h>
+
+#include <optional>
+#include <string>
+
+#include "FeatureFlags.h"
 #include "Paint.h"
 #include "SkPathMeasure.h"
 #include "Typeface.h"
@@ -43,9 +46,17 @@
     minikinPaint.wordSpacing = paint->getWordSpacing();
     minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
-    minikinPaint.familyVariant = paint->getFamilyVariant();
     minikinPaint.fontStyle = resolvedFace->fStyle;
     minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+
+    const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
+    if (familyVariant.has_value()) {
+        minikinPaint.familyVariant = familyVariant.value();
+    } else {
+        minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
+                                             ? minikin::FamilyVariant::ELEGANT
+                                             : minikin::FamilyVariant::DEFAULT;
+    }
     return minikinPaint;
 }
 
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4a8f3e1..caffdfc 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -94,9 +94,10 @@
 
     uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
 
+    void resetFamilyVariant() { mFamilyVariant.reset(); }
     void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; }
 
-    minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; }
+    std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; }
 
     void setStartHyphenEdit(uint32_t startHyphen) {
         mHyphenEdit = minikin::packHyphenEdit(
@@ -171,7 +172,7 @@
     float mWordSpacing = 0;
     std::string mFontFeatureSettings;
     uint32_t mMinikinLocaleListId;
-    minikin::FamilyVariant mFamilyVariant;
+    std::optional<minikin::FamilyVariant> mFamilyVariant;
     uint32_t mHyphenEdit = 0;
     // The native Typeface object has the same lifetime of the Java Typeface
     // object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 7aef7a5..8c71d6f 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -593,7 +593,7 @@
         return result;
     }
 
-    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
+    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) {
         const int kElegantTop = 2500;
         const int kElegantBottom = -1000;
         const int kElegantAscent = 1900;
@@ -622,6 +622,17 @@
             metrics->fLeading = size * kElegantLeading / 2048;
             spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
         }
+
+        if (useLocale) {
+            minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+            minikin::MinikinExtent extent =
+                    typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+            metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
+            metrics->fDescent = std::max(extent.descent, metrics->fDescent);
+            metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
+            metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom);
+        }
+
         return spacing;
     }
 
@@ -634,7 +645,7 @@
                 MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
 
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
 
         metrics.fAscent = extent.ascent;
         metrics.fDescent = extent.descent;
@@ -686,20 +697,21 @@
         }
     }
 
-    static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+    static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+                                 jboolean useLocale) {
         SkFontMetrics metrics;
-        SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
+        SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale);
         GraphicsJNI::set_metrics(env, metricsObj, metrics);
         return SkScalarToFloat(spacing);
     }
 
-    static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+    static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+                                  jboolean useLocale) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, useLocale);
         return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
     }
 
-
     // ------------------ @CriticalNative ---------------------------
 
     static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
@@ -923,15 +935,39 @@
         obj->setMinikinLocaleListId(minikinLocaleListId);
     }
 
-    static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
+    // Note: Following three values must be equal to the ones in Java file: Paint.java.
+    constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1;
+    constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+    constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
+    static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT;
+        const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant();
+        if (familyVariant.has_value()) {
+            if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) {
+                return ELEGANT_TEXT_HEIGHT_ENABLED;
+            } else {
+                return ELEGANT_TEXT_HEIGHT_DISABLED;
+            }
+        } else {
+            return ELEGANT_TEXT_HEIGHT_UNSET;
+        }
     }
 
-    static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) {
+    static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        obj->setFamilyVariant(
-                aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT);
+        switch (value) {
+            case ELEGANT_TEXT_HEIGHT_ENABLED:
+                obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT);
+                return;
+            case ELEGANT_TEXT_HEIGHT_DISABLED:
+                obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT);
+                return;
+            case ELEGANT_TEXT_HEIGHT_UNSET:
+            default:
+                obj->resetFamilyVariant();
+                return;
+        }
     }
 
     static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
@@ -1002,19 +1038,19 @@
 
     static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         return SkScalarToFloat(metrics.fAscent);
     }
 
     static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         return SkScalarToFloat(metrics.fDescent);
     }
 
     static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         SkScalar position;
         if (metrics.hasUnderlinePosition(&position)) {
             return SkScalarToFloat(position);
@@ -1026,7 +1062,7 @@
 
     static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         SkScalar thickness;
         if (metrics.hasUnderlineThickness(&thickness)) {
             return SkScalarToFloat(thickness);
@@ -1121,9 +1157,9 @@
         {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
         {"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
          (void*)PaintGlue::setFontFeatureSettings},
-        {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+        {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F",
          (void*)PaintGlue::getFontMetrics},
-        {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+        {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I",
          (void*)PaintGlue::getFontMetricsInt},
 
         // --------------- @CriticalNative ------------------
@@ -1166,8 +1202,8 @@
         {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
         {"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
          (void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
-        {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
-        {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+        {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight},
+        {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight},
         {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
         {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
         {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 1042703..814b682 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@
         , mTotalSize("bytes", 0)
         , mPurgeableSize("bytes", 0) {}
 
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
     for (auto& resource : mResourceMap) {
-        if (SkStrContains(resourceName, resource.first)) {
+        if (resourceName.find(resource.first) != std::string::npos) {
             return resource.second;
         }
     }
-    return nullptr;
+    return std::nullopt;
 }
 
 void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@
         }
 
         // find the type if one exists
-        const char* type;
+        std::string type;
         auto typeResult = mCurrentValues.find("type");
         if (typeResult != mCurrentValues.end()) {
             type = typeResult->second.units;
@@ -71,14 +71,13 @@
         }
 
         // compute the type if we are itemizing or use the default "size" if we are not
-        const char* key = (mItemizeType) ? type : sizeResult->first;
-        SkASSERT(key != nullptr);
+        std::string key = (mItemizeType) ? type : sizeResult->first;
 
         // compute the top level element name using either the map or category key
-        const char* resourceName = mapName(mCurrentElement.c_str());
-        if (mCategoryKey != nullptr) {
+        std::optional<std::string> resourceName = mapName(mCurrentElement);
+        if (mCategoryKey) {
             // find the category if one exists
-            auto categoryResult = mCurrentValues.find(mCategoryKey);
+            auto categoryResult = mCurrentValues.find(*mCategoryKey);
             if (categoryResult != mCurrentValues.end()) {
                 resourceName = categoryResult->second.units;
             } else if (mItemizeType) {
@@ -87,11 +86,11 @@
         }
 
         // if we don't have a pretty name then use the dumpName
-        if (resourceName == nullptr) {
-            resourceName = mCurrentElement.c_str();
+        if (!resourceName) {
+            resourceName = mCurrentElement;
         }
 
-        auto result = mResults.find(resourceName);
+        auto result = mResults.find(*resourceName);
         if (result != mResults.end()) {
             auto& resourceValues = result->second;
             typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@
             TraceValue sizeValue = sizeResult->second;
             mCurrentValues.clear();
             mCurrentValues.insert({key, sizeValue});
-            mResults.insert({resourceName, mCurrentValues});
+            mResults.insert({*resourceName, mCurrentValues});
         }
     }
 
@@ -139,8 +138,9 @@
             for (const auto& typedValue : namedItem.second) {
                 TraceValue traceValue = convertUnits(typedValue.second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
-                log.appendFormat("    %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
-                                 traceValue.units, traceValue.count, entry);
+                log.appendFormat("    %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+                                 traceValue.value, traceValue.units.c_str(), traceValue.count,
+                                 entry);
             }
         } else {
             auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@
                 TraceValue traceValue = convertUnits(result->second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
                 log.appendFormat("  %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
-                                 traceValue.value, traceValue.units, traceValue.count, entry);
+                                 traceValue.value, traceValue.units.c_str(), traceValue.count,
+                                 entry);
             }
         }
     }
@@ -156,7 +157,7 @@
 
 size_t SkiaMemoryTracer::total() {
     processElement();
-    if (!strcmp("bytes", mTotalSize.units)) {
+    if ("bytes" == mTotalSize.units) {
         return mTotalSize.value;
     }
     return 0;
@@ -166,16 +167,16 @@
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
     log.appendFormat("  %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
-                     total.value, total.units, purgeable.value, purgeable.units);
+                     total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
 }
 
 SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
     TraceValue output(value);
-    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+    if ("bytes" == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "KB";
     }
-    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+    if ("KB" == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "MB";
     }
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b04..dbfc86b 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
-#include <SkString.h>
 #include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
 #include <utils/String8.h>
 #include <unordered_map>
 #include <vector>
@@ -60,17 +61,17 @@
         TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
         TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
 
-        const char* units;
+        std::string units;
         float value;
         int count;
     };
 
-    const char* mapName(const char* resourceName);
+    std::optional<std::string> mapName(const std::string& resourceName);
     void processElement();
     TraceValue convertUnits(const TraceValue& value);
 
     const std::vector<ResourcePair> mResourceMap;
-    const char* mCategoryKey = nullptr;
+    std::optional<std::string> mCategoryKey;
     const bool mItemizeType;
 
     // variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@
 
     // variables storing information on the current node being dumped
     std::string mCurrentElement;
-    std::unordered_map<const char*, TraceValue> mCurrentValues;
+    std::unordered_map<std::string, TraceValue> mCurrentValues;
 
     // variable that stores the final format of the data after the individual elements are processed
-    std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+    std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
 };
 
 } /* namespace skiapipeline */
 } /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b00fc69..7fac0c9 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,6 +139,7 @@
     mRenderNodes.clear();
     mRenderThread.cacheManager().unregisterCanvasContext(this);
     mRenderThread.renderState().removeContextCallback(this);
+    mHintSessionWrapper->destroy();
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -561,7 +562,11 @@
 void CanvasContext::draw(bool solelyTextureViewUpdates) {
     if (auto grContext = getGrContext()) {
         if (grContext->abandoned()) {
-            LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+            if (grContext->isDeviceLost()) {
+                LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+                return;
+            }
+            LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
             return;
         }
     }
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1c3399a..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,7 +158,6 @@
 void HintSessionWrapper::sendLoadIncreaseHint() {
     if (!init()) return;
     mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
-    mLastFrameNotification = systemTime();
 }
 
 bool HintSessionWrapper::alive() {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 90850d3..22c5862 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -26,6 +26,7 @@
 #include <gui/TraceUtils.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <ui/FatVector.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
@@ -435,7 +436,7 @@
     options.fContextDeleteContext = this;
     options.fContextDeleteProc = onGrContextReleased;
 
-    return GrDirectContext::MakeVulkan(backendContext, options);
+    return GrDirectContexts::MakeVulkan(backendContext, options);
 }
 
 VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index a14ae1c..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -259,6 +259,31 @@
 
 TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
     EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+    EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, report an actual duration
+    mWrapper->reportActualWorkDuration(5_ms);
+
+    // Then, run the delayed deletion after sending the update
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
     EXPECT_CALL(*sMockBinding,
                 fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
             .Times(1);
@@ -272,16 +297,46 @@
     // First schedule the deletion
     scheduleDelayedDestroyManaged();
 
-    // Then, send a hint to update the timestamp
+    // Then, send a load_up hint
     mWrapper->sendLoadIncreaseHint();
 
     // Then, run the delayed deletion after sending the update
     allowDelayedDestructionToStart();
     waitForDelayedDestructionToFinish();
 
-    // Ensure it didn't close within the timeframe of the test
+    // Ensure it closed within the timeframe of the test
     Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
     EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, kill the session
+    mWrapper->destroy();
+
+    // Verify it died
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+    // Then, run the delayed deletion after manually killing the session
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close again and is still dead
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
 }
 
 }  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/location/api/current.txt b/location/api/current.txt
index 33effdd..0c23d8c 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -412,7 +412,9 @@
     field public static final int TYPE_GPS_L1CA = 257; // 0x101
     field public static final int TYPE_GPS_L2CNAV = 258; // 0x102
     field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
-    field public static final int TYPE_IRN_L5CA = 1793; // 0x701
+    field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
+    field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
+    field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701
     field public static final int TYPE_QZS_L1CA = 1025; // 0x401
     field public static final int TYPE_SBS = 513; // 0x201
     field public static final int TYPE_UNKNOWN = 0; // 0x0
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 637f905..32e636f 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -16,10 +16,12 @@
 
 package android.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.location.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -41,7 +43,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2,
             TYPE_SBS, TYPE_GLO_L1CA, TYPE_QZS_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_BDS_CNAV1,
-            TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA})
+            TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA, TYPE_IRN_L5, TYPE_IRN_L1})
     public @interface GnssNavigationMessageType {}
 
     // The following enumerations must be in sync with the values declared in gps.h
@@ -74,8 +76,18 @@
     public static final int TYPE_GAL_I = 0x0601;
     /** Galileo F/NAV message contained in the structure. */
     public static final int TYPE_GAL_F = 0x0602;
-    /** IRNSS L5 C/A message contained in the structure. */
+    /**
+     * NavIC L5 C/A message contained in the structure.
+     * @deprecated Use {@link #TYPE_IRN_L5} instead.
+     */
+    @Deprecated
     public static final int TYPE_IRN_L5CA = 0x0701;
+    /** NavIC L5 message contained in the structure. */
+    @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
+    public static final int TYPE_IRN_L5 = 0x0702;
+    /** NavIC L1 message contained in the structure. */
+    @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
+    public static final int TYPE_IRN_L1 = 0x0703;
 
     /**
      * The status of the GNSS Navigation Message
@@ -254,8 +266,15 @@
             case TYPE_GAL_F:
                 return "Galileo F";
             case TYPE_IRN_L5CA:
-                return "IRNSS L5 C/A";
+                return "NavIC L5 C/A";
             default:
+                if (Flags.gnssApiNavicL1()) {
+                    if (mType == TYPE_IRN_L5) {
+                        return "NavIC L5";
+                    } else if (mType == TYPE_IRN_L1) {
+                        return "NavIC L1";
+                    }
+                }
                 return "<Invalid:" + mType + ">";
         }
     }
@@ -303,9 +322,12 @@
      * navigation message, in the range of 1-25 (Subframe 1, 2, 3 does not contain a 'frame id' and
      * this value can be set to -1.)</li>
      * <li> For Beidou CNAV1 this refers to the page type number in the range of 1-63.</li>
-     * <li> For IRNSS L5 C/A subframe 3 and 4, this value corresponds to the Message Id of the
+     * <li> For NavIC L5 subframe 3 and 4, this value corresponds to the Message Id of the
      * navigation message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type
      * id and this value can be set to -1.)</li>
+     * <li> For NavIC L1 subframe 3, this value corresponds to the Message Id of the navigation
+     * message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type id and this
+     * value can be set to -1.)</li>
      * </ul>
      */
     @IntRange(from = -1, to = 120)
@@ -339,8 +361,10 @@
      * navigation message, in the range of 1-3.</li>
      * <li> For Beidou CNAV2, the submessage id corresponds to the message type, in the range
      * 1-63.</li>
-     * <li> For IRNSS L5 C/A, the submessage id corresponds to the subframe number of the
-     * navigation message, in the range of 1-4.</li>
+     * <li> For NavIC L5, the submessage id corresponds to the subframe number of the navigation
+     * message, in the range of 1-4.</li>
+     * <li> For NavIC L1, the submessage id corresponds to the subframe number of the navigation
+     * message, in the range of 1-3.</li>
      * </ul>
      */
     @IntRange(from = 1)
@@ -363,7 +387,7 @@
      * <p>The bytes (or words) specified using big endian format (MSB first).
      *
      * <ul>
-     * <li>For GPS L1 C/A, IRNSS L5 C/A, Beidou D1 &amp; Beidou D2, each subframe contains 10
+     * <li>For GPS L1 C/A, NavIC L5, Beidou D1 &amp; Beidou D2, each subframe contains 10
      * 30-bit words. Each word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip
      * B31 and B32), with MSB first, for a total of 40 bytes, covering a time period of 6, 6, and
      * 0.6 seconds, respectively.</li>
@@ -383,6 +407,9 @@
      * 75 bytes. subframe #3 consists of 264 data bits that should be fit into 33 bytes.</li>
      * <li>For Beidou CNAV2, each subframe consists of 288 data bits, that should be fit into 36
      * bytes.</li>
+     * <li> For NavIC L1, subframe #1 consists of 9 data bits that should be fit into 2 bytes (skip
+     * B10-B16). subframe #2 consists of 600 bits that should be fit into 75 bytes. subframe #3
+     * consists of 274 data bits that should be fit into 35 bytes (skip B275-B280).</li>
      * </ul>
      */
     @NonNull
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
index 16c3f2e..2dd67f0 100644
--- a/location/java/android/location/GnssSignalType.java
+++ b/location/java/android/location/GnssSignalType.java
@@ -34,8 +34,7 @@
     /**
      * Creates a {@link GnssSignalType} with a full list of parameters.
      *
-     * @param constellationType the constellation type as defined in
-     * {@link GnssStatus.ConstellationType}
+     * @param constellationType the constellation type
      * @param carrierFrequencyHz the carrier frequency in Hz
      * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
      */
@@ -66,7 +65,7 @@
         this.mCodeType = codeType;
     }
 
-    /** Returns the {@link GnssStatus.ConstellationType}. */
+    /** Returns the constellation type. */
     @GnssStatus.ConstellationType
     public int getConstellationType() {
         return mConstellationType;
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
new file mode 100644
index 0000000..c471a27
--- /dev/null
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -0,0 +1,8 @@
+package: "android.location.flags"
+
+flag {
+    name: "gnss_api_navic_l1"
+    namespace: "location"
+    description: "Flag for GNSS API for NavIC L1"
+    bug: "302199306"
+}
\ No newline at end of file
diff --git a/media/Android.bp b/media/Android.bp
index f69dd3c..3493408 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@
     name: "soundtrigger_middleware-aidl",
     unstable: true,
     local_include_dir: "aidl",
+    defaults: [
+        "latest_android_media_audio_common_types_import_interface",
+        "latest_android_media_soundtrigger_types_import_interface",
+    ],
     backend: {
         java: {
             sdk_version: "module_current",
@@ -32,8 +36,6 @@
         "aidl/android/media/soundtrigger_middleware/*.aidl",
     ],
     imports: [
-        "android.media.audio.common.types-V2",
-        "android.media.soundtrigger.types-V1",
         "media_permission-aidl",
     ],
 }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 230fb07..bf9419fe 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -875,18 +875,7 @@
         /**
          * Sets the attribute describing what is the intended use of the audio signal,
          * such as alarm or ringtone.
-         * @param usage one of {@link AttributeSdkUsage#USAGE_UNKNOWN},
-         *     {@link AttributeSdkUsage#USAGE_MEDIA},
-         *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION},
-         *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING},
-         *     {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
-         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE},
-         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANT},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION},
-         *     {@link AttributeSdkUsage#USAGE_GAME}.
+         * @param usage the usage to set.
          * @return the same Builder instance.
          */
         public Builder setUsage(@AttributeSdkUsage int usage) {
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index af3c295..5a27435 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -68,7 +68,7 @@
     /**
      * The unique address of the device. Some devices don't have addresses, only an empty string.
      */
-    private final @NonNull String mAddress;
+    private @NonNull String mAddress;
     /**
      * The non-unique name of the device. Some devices don't have names, only an empty string.
      * Should not be used as a unique identifier for a device.
@@ -188,6 +188,21 @@
 
     /**
      * @hide
+     * Copy Constructor.
+     * @param ada the copied AudioDeviceAttributes
+     */
+    public AudioDeviceAttributes(AudioDeviceAttributes ada) {
+        mRole = ada.getRole();
+        mType = ada.getType();
+        mAddress = ada.getAddress();
+        mName = ada.getName();
+        mNativeType = ada.getInternalType();
+        mAudioProfiles = ada.getAudioProfiles();
+        mAudioDescriptors = ada.getAudioDescriptors();
+    }
+
+    /**
+     * @hide
      * Returns the role of a device
      * @return the role
      */
@@ -218,6 +233,15 @@
 
     /**
      * @hide
+     * Sets the device address. Only used by audio service.
+     */
+    public void setAddress(@NonNull String address) {
+        Objects.requireNonNull(address);
+        mAddress = address;
+    }
+
+    /**
+     * @hide
      * Returns the name of the audio device, or an empty string for devices without one
      * @return the device name
      */
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index a311296..b002bbf 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1282,11 +1282,8 @@
          *    {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},
          *    {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
          *    {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}.
-         *    <p> For a valid {@link AudioTrack} channel position mask,
-         *    the following conditions apply:
-         *    <br> (1) at most {@link AudioSystem#OUT_CHANNEL_COUNT_MAX} channel positions may be
-         *    used;
-         *    <br> (2) right/left pairs should be matched.
+         *    <p> For output or {@link AudioTrack}, channel position masks which do not contain
+         *    matched left/right pairs are invalid.
          *    <p> For input or {@link AudioRecord}, the mask should be
          *    {@link AudioFormat#CHANNEL_IN_MONO} or
          *    {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index adc0e16..9ad5c3e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -20,6 +20,8 @@
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
 import static android.content.Context.DEVICE_ID_DEFAULT;
 
+import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -1060,8 +1062,17 @@
      * @see #isVolumeFixed()
      */
     public void adjustVolume(int direction, @PublicVolumeFlags int flags) {
-        MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
-        helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
+        if (autoPublicVolumeApiHardening()) {
+            final IAudioService service = getService();
+            try {
+                service.adjustVolume(direction, flags);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+            helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
+        }
     }
 
     /**
@@ -1090,8 +1101,17 @@
      */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType,
             @PublicVolumeFlags int flags) {
-        MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
-        helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
+        if (autoPublicVolumeApiHardening()) {
+            final IAudioService service = getService();
+            try {
+                service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+            helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
+        }
     }
 
     /** @hide */
@@ -1473,8 +1493,7 @@
      * Returns the volume group id associated to the given {@link AudioAttributes}.
      *
      * @param attributes The {@link AudioAttributes} to consider.
-     * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given
-     * {@link AudioAttributes} if found,
+     * @return audio volume group id supporting the given {@link AudioAttributes} if found,
      * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise.
      */
     public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
@@ -1589,7 +1608,7 @@
      * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
      * the volume group id supporting the given {@link AudioAttributes}.
      *
-     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @param groupId of the audio volume group to consider.
      * @param direction The direction to adjust the volume. One of
      *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
      *            {@link #ADJUST_SAME}.
@@ -1633,8 +1652,8 @@
      * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
      * the volume group id supporting the given {@link AudioAttributes}.
      *
-     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
-     * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id.
+     * @param groupId of the audio volume group to consider.
+     * @return The mute state for the given audio volume group id.
      * @see #adjustVolumeGroupVolume(int, int, int)
      */
     public boolean isVolumeGroupMuted(int groupId) {
@@ -4659,24 +4678,24 @@
         Objects.requireNonNull(afr);
         Objects.requireNonNull(clientFakeId);
         int status;
-        try {
-            status = getService().requestAudioFocusForTest(afr.getAudioAttributes(),
-                    afr.getFocusGain(),
-                    mICallBack,
-                    mAudioFocusDispatcher,
-                    clientFakeId, "com.android.test.fakeclient",
-                    afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST,
-                    clientFakeUid, clientTargetSdk);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
-            // default path with no external focus policy
-            return status;
-        }
-
         BlockingFocusResultReceiver focusReceiver;
         synchronized (mFocusRequestsLock) {
+            try {
+                status = getService().requestAudioFocusForTest(afr.getAudioAttributes(),
+                        afr.getFocusGain(),
+                        mICallBack,
+                        mAudioFocusDispatcher,
+                        clientFakeId, "com.android.test.fakeclient",
+                        afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST,
+                        clientFakeUid, clientTargetSdk);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+                // default path with no external focus policy
+                return status;
+            }
+
             focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId);
         }
 
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index 0f962f9..4e61549 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -226,16 +226,15 @@
          *
          * An Integer value representing presentation content classifier.
          *
-         * @see AudioPresentation.ContentClassifier
-         * One of {@link AudioPresentation#CONTENT_UNKNOWN},
-         *     {@link AudioPresentation#CONTENT_MAIN},
-         *     {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},
-         *     {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED},
-         *     {@link AudioPresentation#CONTENT_HEARING_IMPAIRED},
-         *     {@link AudioPresentation#CONTENT_DIALOG},
-         *     {@link AudioPresentation#CONTENT_COMMENTARY},
-         *     {@link AudioPresentation#CONTENT_EMERGENCY},
-         *     {@link AudioPresentation#CONTENT_VOICEOVER}.
+         * @see AudioPresentation#CONTENT_UNKNOWN
+         * @see AudioPresentation#CONTENT_MAIN
+         * @see AudioPresentation#CONTENT_MUSIC_AND_EFFECTS
+         * @see AudioPresentation#CONTENT_VISUALLY_IMPAIRED
+         * @see AudioPresentation#CONTENT_HEARING_IMPAIRED
+         * @see AudioPresentation#CONTENT_DIALOG
+         * @see AudioPresentation#CONTENT_COMMENTARY
+         * @see AudioPresentation#CONTENT_EMERGENCY
+         * @see AudioPresentation#CONTENT_VOICEOVER
          */
         @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER =
                 createKey("presentation-content-classifier", Integer.class);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0e7718b..8584dbc 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -498,6 +498,10 @@
             in String packageName, int uid, int pid, in UserHandle userHandle,
             int targetSdkVersion);
 
+    oneway void adjustVolume(int direction, int flags);
+
+    oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
+
     boolean isMusicActive(in boolean remotely);
 
     int getDeviceMaskForStream(in int streamType);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 7eb0c76..4a5b4f2 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -74,8 +74,7 @@
 
     // Methods for MediaRouter2Manager
     List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager);
-    RoutingSessionInfo getSystemSessionInfoForPackage(
-            IMediaRouter2Manager manager, String packageName);
+    RoutingSessionInfo getSystemSessionInfoForPackage(String packageName);
     void registerManager(IMediaRouter2Manager manager, String packageName);
     void unregisterManager(IMediaRouter2Manager manager);
     void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 73f15f2..1e57be2 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -49,7 +49,7 @@
     oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
-    oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
+    oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa, float volume);
     oneway void stopAsync();
 
     /** Return the title of the media. */
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 91fa873..cccf6f1 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -18,6 +18,9 @@
 
 import static android.media.MediaRouter2Utils.toUniqueId;
 
+import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -141,6 +144,8 @@
                 TYPE_WIRED_HEADPHONES,
                 TYPE_BLUETOOTH_A2DP,
                 TYPE_HDMI,
+                TYPE_HDMI_ARC,
+                TYPE_HDMI_EARC,
                 TYPE_USB_DEVICE,
                 TYPE_USB_ACCESSORY,
                 TYPE_DOCK,
@@ -206,6 +211,22 @@
     public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
 
     /**
+     * Indicates the route is an Audio Return Channel of an HDMI connection.
+     *
+     * @see #getType
+     */
+    @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
+
+    /**
+     * Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
+     *
+     * @see #getType
+     */
+    @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
+
+    /**
      * Indicates the route is a USB audio device.
      *
      * @see #getType
@@ -907,6 +928,10 @@
                 return "BLUETOOTH_A2DP";
             case TYPE_HDMI:
                 return "HDMI";
+            case TYPE_HDMI_ARC:
+                return "HDMI_ARC";
+            case TYPE_HDMI_EARC:
+                return "HDMI_EARC";
             case TYPE_DOCK:
                 return "DOCK";
             case TYPE_USB_DEVICE:
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8c63580..f68eb3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -52,6 +52,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -310,8 +311,11 @@
                 IMediaRouterService.Stub.asInterface(
                         ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
 
+        mSystemController =
+                new SystemRoutingController(
+                        ProxyMediaRouter2Impl.getSystemSessionInfoImpl(
+                                mMediaRouterService, clientPackageName));
         mImpl = new ProxyMediaRouter2Impl(context, clientPackageName);
-        mSystemController = new SystemRoutingController(mImpl.getSystemSessionInfo());
     }
 
     /**
@@ -380,9 +384,9 @@
      * @see #setRouteListingPreference(RouteListingPreference)
      */
     @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
-    public void registerRouteListingPreferenceCallback(
+    public void registerRouteListingPreferenceUpdatedCallback(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+            @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null");
 
@@ -395,15 +399,20 @@
 
     /**
      * Unregisters the given callback to not receive {@link RouteListingPreference} change events.
+     *
+     * @see #registerRouteListingPreferenceUpdatedCallback(Executor, Consumer)
      */
     @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
-    public void unregisterRouteListingPreferenceCallback(
-            @NonNull RouteListingPreferenceCallback callback) {
+    public void unregisterRouteListingPreferenceUpdatedCallback(
+            @NonNull Consumer<RouteListingPreference> callback) {
         Objects.requireNonNull(callback, "callback must not be null");
 
         if (!mListingPreferenceCallbackRecords.remove(
                 new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) {
-            Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback");
+            Log.w(
+                    TAG,
+                    "unregisterRouteListingPreferenceUpdatedCallback: Ignoring an unknown"
+                        + " callback");
         }
     }
 
@@ -463,7 +472,7 @@
     /**
      * Returns the current {@link RouteListingPreference} of the target router.
      *
-     * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+     * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns
      * the last {@link RouteListingPreference} set by the process this router was created for.
      *
      * @see #setRouteListingPreference(RouteListingPreference)
@@ -1116,9 +1125,7 @@
     private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) {
         for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) {
             record.mExecutor.execute(
-                    () ->
-                            record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged(
-                                    preference));
+                    () -> record.mRouteListingPreferenceCallback.accept(preference));
         }
     }
 
@@ -1205,22 +1212,6 @@
         public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
     }
 
-    /** Callback for receiving events related to {@link RouteListingPreference}. */
-    @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
-    public abstract static class RouteListingPreferenceCallback {
-
-        @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
-        public RouteListingPreferenceCallback() {}
-
-        /**
-         * Called when the {@link RouteListingPreference} changes.
-         *
-         * @see #getRouteListingPreference
-         */
-        @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
-        public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
-    }
-
     /** Callback for receiving events on media transfer. */
     public abstract static class TransferCallback {
         /**
@@ -1771,11 +1762,11 @@
 
     private static final class RouteListingPreferenceCallbackRecord {
         public final Executor mExecutor;
-        public final RouteListingPreferenceCallback mRouteListingPreferenceCallback;
+        public final Consumer<RouteListingPreference> mRouteListingPreferenceCallback;
 
         /* package */ RouteListingPreferenceCallbackRecord(
                 @NonNull Executor executor,
-                @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+                @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
             mExecutor = executor;
             mRouteListingPreferenceCallback = routeListingPreferenceCallback;
         }
@@ -2057,15 +2048,7 @@
 
         @Override
         public RoutingSessionInfo getSystemSessionInfo() {
-            RoutingSessionInfo result;
-            try {
-                result =
-                        mMediaRouterService.getSystemSessionInfoForPackage(
-                                mClient, mClientPackageName);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-            return ensureClientPackageNameForSystemSession(result);
+            return getSystemSessionInfoImpl(mMediaRouterService, mClientPackageName);
         }
 
         /**
@@ -2430,6 +2413,23 @@
         }
 
         /**
+         * Retrieves the system session info for the given package.
+         *
+         * <p>The returned routing session is guaranteed to have a non-null {@link
+         * RoutingSessionInfo#getClientPackageName() client package name}.
+         *
+         * <p>Extracted into a static method to allow calling this from the constructor.
+         */
+        /* package */ static RoutingSessionInfo getSystemSessionInfoImpl(
+                @NonNull IMediaRouterService service, @NonNull String clientPackageName) {
+            try {
+                return service.getSystemSessionInfoForPackage(clientPackageName);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client
          * package name} to {@link #mClientPackageName} if empty and returns the session.
          *
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 3abfc629..830708c 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -377,7 +377,7 @@
     @Nullable
     public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) {
         try {
-            return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName);
+            return mMediaRouterService.getSystemSessionInfoForPackage(packageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9a6ed4..9ced2a4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,7 @@
 
 package android.media.audiopolicy;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,6 +49,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.audio.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -417,6 +419,7 @@
      * @return {@link AudioManager#SUCCESS} if the update was successful,
      *  {@link AudioManager#ERROR} otherwise.
      */
+    @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API)
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public int updateMixingRules(
             @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index a354f91..386534b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -27,3 +27,17 @@
     description: "Disables the broadcast receiver that prevents scanning when the screen is off."
     bug: "304234628"
 }
+
+flag {
+    namespace: "media_solutions"
+    name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+    description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+    bug: "293743975"
+}
+
+flag {
+    namespace: "media_solutions"
+    name: "enable_waiting_state_for_system_session_creation_request"
+    description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
+    bug: "307723189"
+}
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 6e2aaab..c54bfce 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -16,6 +16,9 @@
 
 package android.media.midi;
 
+import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Service;
@@ -38,7 +41,7 @@
  * of {@link MidiReceiver}s for sending data out the output ports.
  *
  * Unlike traditional MIDI byte streams, only complete UMPs should be sent.
- * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal.
+ * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal.
  *
  * <p>To extend this class, you must declare the service in your manifest file with
  * an intent filter with the {@link #SERVICE_INTERFACE} action
@@ -54,9 +57,11 @@
  *             android:resource="@xml/device_info" />
  * &lt;/service></pre>
  */
+@FlaggedApi(FLAG_VIRTUAL_UMP)
 public abstract class MidiUmpDeviceService extends Service {
     private static final String TAG = "MidiUmpDeviceService";
 
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
 
     private IMidiManager mMidiManager;
@@ -75,6 +80,7 @@
         }
     };
 
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     @Override
     public void onCreate() {
         mMidiManager = IMidiManager.Stub.asInterface(
@@ -112,6 +118,7 @@
      * The number of input and output ports must be equal and non-zero.
      * @return list of MidiReceivers
      */
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers();
 
     /**
@@ -120,6 +127,7 @@
      * The number of input and output ports must be equal and non-zero.
      * @return the list of MidiReceivers
      */
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     public final @NonNull List<MidiReceiver> getOutputPortReceivers() {
         if (mServer == null) {
             return new ArrayList<MidiReceiver>();
@@ -132,6 +140,7 @@
      * Returns the {@link MidiDeviceInfo} instance for this service
      * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created
      */
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     public final @Nullable MidiDeviceInfo getDeviceInfo() {
         return mDeviceInfo;
     }
@@ -140,6 +149,7 @@
      * Called to notify when the {@link MidiDeviceStatus} has changed
      * @param status the current status of the MIDI device
      */
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {
     }
 
@@ -147,9 +157,11 @@
      * Called to notify when the virtual MIDI device running in this service has been closed by
      * all its clients
      */
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     public void onClose() {
     }
 
+    @FlaggedApi(FLAG_VIRTUAL_UMP)
     @Override
     public @Nullable IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d294601..24efbd1 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -156,4 +156,60 @@
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
             in @nullable IMediaProjection projection);
+
+    /**
+     * Notifies system server that the permission request was initiated.
+     *
+     * <p>Only used for emitting atoms.
+     *
+     * @param hostUid               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
+     *                              in the SessionCreationSource enum.
+     */
+    @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);
+
+    /**
+     * 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.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    oneway void notifyPermissionRequestDisplayed(int hostUid);
+
+    /**
+     * 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.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    oneway void notifyPermissionRequestCancelled(int hostUid);
+
+    /**
+     * 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.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    oneway void notifyAppSelectorDisplayed(int hostUid);
 }
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index cc9be9c..880ec8f 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -4,3 +4,4 @@
 santoscordon@google.com
 chaviw@google.com
 nmusgrave@google.com
+dakinola@google.com
diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
index 078e832..ec0d7f7 100644
--- a/media/java/android/media/tv/SectionRequest.java
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -81,7 +81,7 @@
     /**
      * Gets the version number of requested session. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
index f38ea9d..10333fe 100644
--- a/media/java/android/media/tv/SectionResponse.java
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -76,7 +76,7 @@
     /**
      * Gets the Version number of requested session. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
index d9587f6..06df07f 100644
--- a/media/java/android/media/tv/TableRequest.java
+++ b/media/java/android/media/tv/TableRequest.java
@@ -129,7 +129,7 @@
     /**
      * Gets the version number of requested table. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index c4fc26e..1daf452 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -269,7 +269,7 @@
     /**
      * Gets the version number of requested table. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 667a9ae..2db4be8 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -939,9 +939,8 @@
                 type = TYPE_HDMI;
                 isHardwareInput = true;
                 hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
-                isConnectedToHdmiSwitch =
-                        hdmiConnectionRelativePosition
-                                != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+                isConnectedToHdmiSwitch = hdmiConnectionRelativePosition
+                                == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW;
             } else if (mTvInputHardwareInfo != null) {
                 id = generateInputId(componentName, mTvInputHardwareInfo);
                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index c616b84f..1c25080 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -38,6 +38,8 @@
 #include <mediadrm/IDrmMetricsConsumer.h>
 #include <mediadrm/IDrm.h>
 #include <utils/Vector.h>
+#include <map>
+#include <string>
 
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
@@ -193,6 +195,11 @@
     jclass classId;
 };
 
+struct DrmExceptionFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -215,6 +222,7 @@
     jclass parcelCreatorClassId;
     KeyStatusFields keyStatus;
     LogMessageFields logMessage;
+    std::map<std::string, DrmExceptionFields> exceptionCtors;
 };
 
 static fields_t gFields;
@@ -245,18 +253,32 @@
     return arrayList;
 }
 
-int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) {
+void resolveDrmExceptionCtor(JNIEnv *env, const char *className) {
+    jclass clazz;
+    jmethodID init;
+    FIND_CLASS(clazz, className);
+    GET_METHOD_ID(init, clazz, "<init>", "(Ljava/lang/String;III)V");
+    gFields.exceptionCtors[std::string(className)] = {
+        .init = init,
+        .classId = static_cast<jclass>(env->NewGlobalRef(clazz))
+        };
+}
+
+void drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) {
     using namespace android::jnihelp;
-    jstring _detailMessage = CreateExceptionMsg(env, msg);
-    int _status = ThrowException(env, className, "(Ljava/lang/String;III)V",
-                                 _detailMessage,
-                                 err.getCdmErr(),
-                                 err.getOemErr(),
-                                 err.getContext());
-    if (_detailMessage != NULL) {
-        env->DeleteLocalRef(_detailMessage);
+
+    if (gFields.exceptionCtors.count(std::string(className)) == 0) {
+        jniThrowException(env, className, msg);
+    } else {
+        jstring _detailMessage = CreateExceptionMsg(env, msg);
+        jobject exception = env->NewObject(gFields.exceptionCtors[std::string(className)].classId,
+            gFields.exceptionCtors[std::string(className)].init, _detailMessage,
+            err.getCdmErr(), err.getOemErr(), err.getContext());
+        env->Throw(static_cast<jthrowable>(exception));
+        if (_detailMessage != NULL) {
+            env->DeleteLocalRef(_detailMessage);
+        }
     }
-    return _status;
 }
 }  // namespace anonymous
 
@@ -952,6 +974,10 @@
     FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
     gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
+
+    resolveDrmExceptionCtor(env, "android/media/NotProvisionedException");
+    resolveDrmExceptionCtor(env, "android/media/ResourceBusyException");
+    resolveDrmExceptionCtor(env, "android/media/DeniedByServerException");
 }
 
 static void android_media_MediaDrm_native_setup(
@@ -2192,4 +2218,4 @@
 int register_android_media_Drm(JNIEnv *env) {
     return AndroidRuntime::registerNativeMethods(env,
                 "android/media/MediaDrm", gMethods, NELEM(gMethods));
-}
+}
\ No newline at end of file
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index e886558..7d79a6c 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml
index 132028c..91c92cc1 100644
--- a/media/tests/MediaFrameworkTest/AndroidTest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidTest.xml
@@ -23,5 +23,6 @@
         <option name="package" value="com.android.mediaframeworktest" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
+        <option name="isolated-storage" value="false"/>
     </test>
 </configuration>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 9be7004..30edfa4 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -44,7 +44,6 @@
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
-        addMediaMetadataRetrieverStateUnitTests(suite);
         addMediaRecorderStateUnitTests(suite);
         addMediaPlayerStateUnitTests(suite);
         addMediaScannerUnitTests(suite);
@@ -70,11 +69,6 @@
     }
 
     // Running all unit tests checking the state machine may be time-consuming.
-    private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
-        suite.addTestSuite(MediaMetadataRetrieverTest.class);
-    }
-
-    // Running all unit tests checking the state machine may be time-consuming.
     private void addMediaRecorderStateUnitTests(TestSuite suite) {
         suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class);
         suite.addTestSuite(MediaRecorderResetStateUnitTest.class);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index bdca474..f70d2d1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,26 +16,34 @@
 
 package com.android.mediaframeworktest.unit;
 
+import static org.junit.Assert.assertTrue;
+
 import android.graphics.Bitmap;
 import android.media.MediaMetadataRetriever;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.FileOutputStream;
 import java.io.IOException;
 
-public class MediaMetadataRetrieverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaMetadataRetrieverTest {
 
     private static final String TAG = "MediaMetadataRetrieverTest";
 
     // Test album art extraction.
     @MediumTest
-    public static void testGetEmbeddedPicture() throws Exception {
+    @Test
+    public void testGetEmbeddedPicture() throws Exception {
         Log.v(TAG, "testGetEmbeddedPicture starts.");
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean supportWMA = MediaProfileReader.getWMAEnable();
@@ -78,7 +86,8 @@
 
     // Test frame capture
     @LargeTest
-    public static void testThumbnailCapture() throws Exception {
+    @Test
+    public void testThumbnailCapture() throws Exception {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean supportWMA = MediaProfileReader.getWMAEnable();
         boolean supportWMV = MediaProfileReader.getWMVEnable();
@@ -134,7 +143,8 @@
     }
 
     @LargeTest
-    public static void testMetadataRetrieval() throws Exception {
+    @Test
+    public void testMetadataRetrieval() throws Exception {
         boolean supportWMA = MediaProfileReader.getWMAEnable();
         boolean supportWMV = MediaProfileReader.getWMVEnable();
         boolean hasFailed = false;
@@ -169,7 +179,8 @@
     // If the specified call order and valid media file is used, no exception
     // should be thrown.
     @MediumTest
-    public static void testBasicNormalMethodCallSequence() throws Exception {
+    @Test
+    public void testBasicNormalMethodCallSequence() throws Exception {
         boolean hasFailed = false;
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         try {
@@ -197,7 +208,8 @@
     // If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must
     // return null.
     @MediumTest
-    public static void testBasicAbnormalMethodCallSequence() {
+    @Test
+    public void testBasicAbnormalMethodCallSequence() {
         boolean hasFailed = false;
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) {
@@ -213,7 +225,8 @@
 
     // Test setDataSource()
     @MediumTest
-    public static void testSetDataSource() throws IOException {
+    @Test
+    public void testSetDataSource() throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean hasFailed = false;
 
diff --git a/nfc-extras/OWNERS b/nfc-extras/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc-extras/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index ffed804..fe8386e 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
@@ -16,6 +16,8 @@
 
 package com.android.nfc_extras;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.HashMap;
 
 import android.content.Context;
@@ -30,6 +32,8 @@
  *
  * There is a 1-1 relationship between an {@link NfcAdapterExtras} object and
  * a {@link NfcAdapter} object.
+ *
+ * TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
  */
 public final class NfcAdapterExtras {
     private static final String TAG = "NfcAdapterExtras";
@@ -72,15 +76,40 @@
     private final NfcAdapter mAdapter;
     final String mPackageName;
 
+    private static INfcAdapterExtras
+        getNfcAdapterExtrasInterfaceFromNfcAdapter(NfcAdapter adapter) {
+        try {
+            Method method = NfcAdapter.class.getDeclaredMethod("getNfcAdapterExtrasInterface");
+            method.setAccessible(true);
+            return (INfcAdapterExtras) method.invoke(adapter);
+        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+                 | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+            Log.e(TAG, "Unable to get context from NfcAdapter");
+        }
+        return null;
+    }
+
     /** get service handles */
     private static void initService(NfcAdapter adapter) {
-        final INfcAdapterExtras service = adapter.getNfcAdapterExtrasInterface();
+        final INfcAdapterExtras service = getNfcAdapterExtrasInterfaceFromNfcAdapter(adapter);
         if (service != null) {
             // Leave stale rather than receive a null value.
             sService = service;
         }
     }
 
+    private static Context getContextFromNfcAdapter(NfcAdapter adapter) {
+        try {
+            Method method = NfcAdapter.class.getDeclaredMethod("getContext");
+            method.setAccessible(true);
+            return (Context) method.invoke(adapter);
+        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+                 | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+            Log.e(TAG, "Unable to get context from NfcAdapter");
+        }
+        return null;
+    }
+
     /**
      * Get the {@link NfcAdapterExtras} for the given {@link NfcAdapter}.
      *
@@ -91,7 +120,7 @@
      * @return the {@link NfcAdapterExtras} object for the given {@link NfcAdapter}
      */
     public static NfcAdapterExtras get(NfcAdapter adapter) {
-        Context context = adapter.getContext();
+        Context context = getContextFromNfcAdapter(adapter);
         if (context == null) {
             throw new UnsupportedOperationException(
                     "You must pass a context to your NfcAdapter to use the NFC extras APIs");
@@ -112,7 +141,7 @@
 
     private NfcAdapterExtras(NfcAdapter adapter) {
         mAdapter = adapter;
-        mPackageName = adapter.getContext().getPackageName();
+        mPackageName = getContextFromNfcAdapter(adapter).getPackageName();
         mEmbeddedEe = new NfcExecutionEnvironment(this);
         mRouteOnWhenScreenOn = new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON,
                 mEmbeddedEe);
@@ -156,12 +185,24 @@
         }
     }
 
+    private static void attemptDeadServiceRecoveryOnNfcAdapter(NfcAdapter adapter, Exception e) {
+        try {
+            Method method = NfcAdapter.class.getDeclaredMethod(
+                    "attemptDeadServiceRecovery", Exception.class);
+            method.setAccessible(true);
+            method.invoke(adapter, e);
+        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+                 | IllegalAccessException | IllegalAccessError | InvocationTargetException ex) {
+            Log.e(TAG, "Unable to attempt dead service recovery on NfcAdapter");
+        }
+    }
+
     /**
      * NFC service dead - attempt best effort recovery
      */
     void attemptDeadServiceRecovery(Exception e) {
         Log.e(TAG, "NFC Adapter Extras dead - attempting to recover");
-        mAdapter.attemptDeadServiceRecovery(e);
+        attemptDeadServiceRecoveryOnNfcAdapter(mAdapter, e);
         initService(mAdapter);
     }
 
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 58bcd1d..e71597a 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,11 @@
     backend: {
         java: {
             sdk_version: "module_current",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.nfcservices",
+            ],
+
         },
         rust: {
             enabled: true,
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 7ca172e..4eb8de6 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -37,7 +37,7 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Zezwolić urządzeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; na wykonanie tego działania?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o uprawnienia do strumieniowego odtwarzania treści i innych funkcji systemowych na urządzeniach w pobliżu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
-    <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak nazwa osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak imię i nazwisko osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
     <string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 222877b..88f12046 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -102,6 +102,8 @@
         <item name="android:layout_height">36dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:paddingLeft">6dp</item>
+        <item name="android:paddingRight">6dp</item>
         <item name="android:background">@drawable/btn_negative_multiple_devices</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
     </style>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 118a77c..c25fa99 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -70,7 +70,7 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Elvetés"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Szeretné az elmentett jelszavát használni a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni a következőhöz tartozó bejelentkezési adatait: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni bejelentkezési adatait a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Feloldja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> bejelentkezési lehetőségeit?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Mentett azonosítókulcs kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Mentett jelszó kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index ed42f3a..8125ec6 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -70,12 +70,12 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"დახურვა"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"გამოიყენებთ შენახულ პაროლს <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის გამოყენება?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის გამოყენება?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"გსურთ შესვლის ვარიანტების განბლოკვა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"აირჩიეთ შენახული წვდომის გასაღები <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"აირჩიეთ შენახული პაროლი <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
     <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"აირჩიეთ სისტემაში შესვლის ინფორმაცია <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის"</string>
     <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"გსურთ აირჩიოთ ვარიანტი <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის?"</string>
     <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"გსურთ ამ ინფორმაციის გამოყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ში?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"სხვა ხერხით შესვლა"</string>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index b4c5670..e2de2ef 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -70,12 +70,12 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Жабуу"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү үчүн сакталган ачкычты колдоносузбу?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү колдоносузбу?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандайсызбы?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна төмөнкү аккаунт менен киресизби?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү параметрлеринин кулпусу ачылсынбы?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган киргизүүчү ачкычты тандаңыз"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү тандаңыз"</string>
     <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандаңыз"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кайсы аккаунт менен киресиз:"</string>
     <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн параметр тандайсызбы?"</string>
     <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Бул маалыматты <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда пайдаланасызбы?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Башка жол менен кирүү"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 30d1773..4e4894c 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -27,7 +27,7 @@
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Geçiş anahtarlarıyla daha yüksek güvenlik"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Geçiş anahtarı kullandığınızda karmaşık şifreler oluşturmanız veya bunları hatırlamanız gerekmez"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Geçiş anahtarları; parmak iziniz, yüzünüz veya ekran kilidinizi kullanarak oluşturduğunuz şifrelenmiş dijital anahtarlardır"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için şifre anahtarları bir şifre yöneticisine kaydedilir"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için geçiş anahtarları bir şifre yöneticisine kaydedilir"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Geçiş anahtarları hakkında daha fazla bilgi"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Şifresiz teknoloji"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Geçiş anahtarları, şifre kullanmadan oturum açmanıza olanak tanır. Kimliğinizi doğrulayıp geçiş anahtarı oluşturmak için parmak iziniz, yüz tanıma özelliği, PIN veya kaydırma deseni kullanmanız yeterlidir."</string>
@@ -39,10 +39,10 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, geçiş anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> kaydedileceği yeri seçin"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Bilgilerinizi kaydedip bir dahaki sefere daha hızlı oturum açmak için bir şifre yöneticisi seçin"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre anahtarı oluşturulsun mu?"</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için geçiş anahtarı oluşturulsun mu?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre kaydedilsin mi?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
-    <string name="passkey" msgid="632353688396759522">"Şifre anahtarı"</string>
+    <string name="passkey" msgid="632353688396759522">"Geçiş anahtarı"</string>
     <string name="password" msgid="6738570945182936667">"Şifre"</string>
     <string name="passkeys" msgid="5733880786866559847">"Geçiş anahtarlarınızın"</string>
     <string name="passwords" msgid="5419394230391253816">"şifreler"</string>
@@ -61,14 +61,14 @@
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> geçiş anahtarı"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> kimlik bilgileri"</string>
-    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Şifre anahtarı"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Geçiş anahtarı"</string>
     <string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
     <string name="accessibility_close_button" msgid="1163435587545377687">"Kapat"</string>
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Kapat"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı geçiş anahtarınız kullanılsın mı?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifreniz kullanılsın mı?"</string>
     <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgileriniz kullanılsın mı?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma seçeneklerine izin verilsin mi?"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 15a668a..495abe6 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -53,7 +53,7 @@
     <string name="save_password_on_other_device_title" msgid="5829084591948321207">"在其他设备上保存密码?"</string>
     <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"在其他设备上保存登录凭据?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
-    <string name="use_provider_for_all_description" msgid="1998772715863958997">"此 <xliff:g id="USERNAME">%1$s</xliff:g> 密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> 的这个密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
     <string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
     <string name="settings" msgid="6536394145760913145">"设置"</string>
     <string name="use_once" msgid="9027366575315399714">"使用一次"</string>
@@ -70,7 +70,7 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"忽略"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用您为“<xliff:g id="APP_NAME">%1$s</xliff:g>”保存的通行密钥吗?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"要使用已保存的密码登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"是否使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据继续?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"要解锁“<xliff:g id="APP_NAME">%1$s</xliff:g>”的登录选项吗?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"选择一个已保存的通行密钥来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"选择一个已保存的密码来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index f14a5ce..c09bf86 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/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">"憑證管理工具"</string>
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
     <string name="string_continue" msgid="1346732695941131882">"繼續"</string>
     <string name="string_more_options" msgid="2763852250269945472">"儲存其他方式"</string>
diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp
index 0d4af2a..47ca944 100644
--- a/packages/CredentialManager/shared/Android.bp
+++ b/packages/CredentialManager/shared/Android.bp
@@ -16,5 +16,6 @@
         "androidx.core_core-ktx",
         "androidx.credentials_credentials",
         "guava",
+        "hilt_android",
     ],
 }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
index 1cce3ba..5738fee 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
@@ -25,8 +25,11 @@
 import com.android.credentialmanager.TAG
 import com.android.credentialmanager.model.Password
 import com.android.credentialmanager.model.Request
+import javax.inject.Inject
+import javax.inject.Singleton
 
-class PasswordRepository {
+@Singleton
+class PasswordRepository @Inject constructor() {
 
     suspend fun selectPassword(
         password: Password,
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
index 5ab5ab9..1973fc1 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
@@ -16,17 +16,20 @@
 
 package com.android.credentialmanager.repository
 
-import android.app.Application
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.util.Log
 import com.android.credentialmanager.TAG
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.parse
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
 
-class RequestRepository(
-    private val application: Application,
+@Singleton
+class RequestRepository @Inject constructor(
+        private val packageManager: PackageManager,
 ) {
 
     private val _requests = MutableStateFlow<Request?>(null)
@@ -34,7 +37,7 @@
 
     suspend fun processRequest(intent: Intent, previousIntent: Intent? = null) {
         val request = intent.parse(
-            packageManager = application.packageManager,
+            packageManager = packageManager,
             previousIntent = previousIntent
         )
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 473d7b6f..477e61d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -61,14 +61,20 @@
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import androidx.credentials.provider.RemoteEntry
 import org.json.JSONObject
+import android.credentials.flags.Flags
 import java.time.Instant
 
+
 fun getAppLabel(
     pm: PackageManager,
     appPackageName: String
 ): String? {
     return try {
-        val pkgInfo = getPackageInfo(pm, appPackageName)
+        val pkgInfo = if (Flags.instantAppsEnabled()) {
+            getPackageInfo(pm, appPackageName)
+        } else {
+            pm.getPackageInfo(appPackageName, PackageManager.PackageInfoFlags.of(0))
+        }
         val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
         applicationInfo.loadSafeLabel(
             pm, 0f,
@@ -91,7 +97,14 @@
         // Test data has only package name not component name.
         // For test data usage only.
         try {
-            val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName)
+            val pkgInfo = if (Flags.instantAppsEnabled()) {
+                getPackageInfo(pm, providerFlattenedComponentName)
+            } else {
+                pm.getPackageInfo(
+                        providerFlattenedComponentName,
+                        PackageManager.PackageInfoFlags.of(0)
+                )
+            }
             val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
             providerLabel =
                 applicationInfo.loadSafeLabel(
@@ -115,7 +128,14 @@
             // Added for mdoc use case where the provider may not need to register a service and
             // instead only relies on the registration api.
             try {
-                val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName)
+                val pkgInfo = if (Flags.instantAppsEnabled()) {
+                    getPackageInfo(pm, providerFlattenedComponentName)
+                } else {
+                    pm.getPackageInfo(
+                            component.packageName,
+                            PackageManager.PackageInfoFlags.of(0)
+                    )
+                }
                 val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
                 providerLabel =
                     applicationInfo.loadSafeLabel(
@@ -143,12 +163,12 @@
     pm: PackageManager,
     packageName: String
 ): PackageInfo {
-    val flags = PackageManager.MATCH_INSTANT
+    val packageManagerFlags = PackageManager.MATCH_INSTANT
 
     return pm.getPackageInfo(
        packageName,
        PackageManager.PackageInfoFlags.of(
-               (flags).toLong())
+               (packageManagerFlags).toLong())
     )
 }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 2318bb9..d35dcb5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -27,14 +27,25 @@
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
 import android.service.autofill.AutofillService
+import android.service.autofill.Dataset
+import android.service.autofill.Field
 import android.service.autofill.FillCallback
 import android.service.autofill.FillRequest
 import android.service.autofill.FillResponse
+import android.service.autofill.InlinePresentation
+import android.service.autofill.Presentations
 import android.service.autofill.SaveCallback
 import android.service.autofill.SaveRequest
 import android.service.credentials.CredentialProviderService
 import android.util.Log
 import android.view.autofill.AutofillId
+import org.json.JSONException
+import android.widget.inline.InlinePresentationSpec
+import androidx.autofill.inline.v1.InlineSuggestionUi
+import com.android.credentialmanager.GetFlowUtils
+import com.android.credentialmanager.getflow.CredentialEntryInfo
+import com.android.credentialmanager.getflow.ProviderDisplayInfo
+import com.android.credentialmanager.getflow.toProviderDisplayInfo
 import org.json.JSONObject
 import java.util.concurrent.Executors
 
@@ -49,11 +60,9 @@
         private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired"
         private const val CRED_OPTIONS_KEY = "credentialOptions"
         private const val TYPE_KEY = "type"
+        private const val REQ_TYPE_KEY = "get"
     }
 
-    private val credentialManager: CredentialManager =
-            getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
-
     override fun onFillRequest(
             request: FillRequest,
             cancellationSignal: CancellationSignal,
@@ -66,16 +75,24 @@
 
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
         if (getCredRequest == null) {
+            Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
             return
         }
+        val credentialManager: CredentialManager =
+                getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
 
         val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
                 GetCandidateCredentialsException> {
             override fun onResult(result: GetCandidateCredentialsResponse) {
                 Log.i(TAG, "getCandidateCredentials onResponse")
-                val fillResponse: FillResponse? = convertToFillResponse(result, request)
-                callback.onSuccess(fillResponse)
+                val fillResponse = convertToFillResponse(result, request)
+                if (fillResponse != null) {
+                    callback.onSuccess(fillResponse)
+                } else {
+                    Log.e(TAG, "Failed to create a FillResponse from the CredentialResponse.")
+                    callback.onFailure("No dataset was created from the CredentialResponse")
+                }
             }
 
             override fun onError(error: GetCandidateCredentialsException) {
@@ -97,7 +114,93 @@
             getCredResponse: GetCandidateCredentialsResponse,
             filLRequest: FillRequest
     ): FillResponse? {
-        TODO("Not yet implemented")
+        val providerList = GetFlowUtils.toProviderList(
+                getCredResponse.candidateProviderDataList,
+                this@CredentialAutofillService)
+        if (providerList.isEmpty()) {
+            return null
+        }
+        var totalEntryCount = 0
+        providerList.forEach { provider ->
+            totalEntryCount += provider.credentialEntryList.size
+        }
+        val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList)
+        val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
+        val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
+        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+        var maxItemCount = totalEntryCount
+        if (inlineMaxSuggestedCount > 0) {
+            maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
+        }
+        var i = 0
+        val fillResponseBuilder = FillResponse.Builder()
+        var emptyFillResponse = true
+
+        providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
+            val primaryEntry = it.sortedCredentialEntryList.first()
+            // In regular CredMan bottomsheet, only one primary entry per username is displayed.
+            // But since the credential requests from different fields are allocated into a single
+            // request for autofill, there will be duplicate primary entries, especially for
+            // username/pw autofill fields. These primary entries will be the same entries except
+            // their autofillIds will point to different autofill fields. Process all primary
+            // fields.
+            // TODO(b/307435163): Merge credential options
+            it.sortedCredentialEntryList.forEach entryLoop@ { credentialEntry ->
+                if (!isSameCredentialEntry(primaryEntry, credentialEntry)) {
+                    // Encountering different credential entry means all the duplicate primary
+                    // entries have been processed.
+                    return@usernameLoop
+                }
+                val autofillId: AutofillId? = credentialEntry
+                        .fillInIntent
+                        ?.getParcelableExtra(
+                                CredentialProviderService.EXTRA_AUTOFILL_ID,
+                                AutofillId::class.java)
+                val pendingIntent = credentialEntry.pendingIntent
+                if (autofillId == null || pendingIntent == null) {
+                    Log.e(TAG, "AutofillId or pendingIntent was missing from the entry.")
+                    return@entryLoop
+                }
+                var inlinePresentation: InlinePresentation? = null
+                // Create inline presentation
+                if (inlinePresentationSpecs != null && i < maxItemCount) {
+                    val spec: InlinePresentationSpec
+                    if (i < inlinePresentationSpecsCount) {
+                        spec = inlinePresentationSpecs[i]
+                    } else {
+                        spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+                    }
+                    val sliceBuilder = InlineSuggestionUi
+                            .newContentBuilder(pendingIntent)
+                            .setTitle(credentialEntry.userName)
+                    inlinePresentation = InlinePresentation(
+                            sliceBuilder.build().slice, spec, /* pinned= */ false)
+                }
+                i++
+
+                val dataSetBuilder = Dataset.Builder()
+                val presentationBuilder = Presentations.Builder()
+                if (inlinePresentation != null) {
+                    presentationBuilder.setInlinePresentation(inlinePresentation)
+                }
+                fillResponseBuilder.addDataset(
+                        dataSetBuilder
+                                .setField(
+                                        autofillId,
+                                        Field.Builder().setPresentations(
+                                                presentationBuilder.build())
+                                                .build())
+                                .setAuthentication(pendingIntent.intentSender)
+                                .setAuthenticationExtras(credentialEntry.fillInIntent.extras)
+                                .build())
+                emptyFillResponse = false
+            }
+        }
+        if (emptyFillResponse) {
+            return null
+        }
+        return fillResponseBuilder.build()
     }
 
     override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
@@ -165,8 +268,12 @@
 
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         for (credentialHint in credentialHints) {
-            convertJsonToCredentialOption(credentialHint, autofillId)
-                    .let { credentialOptions.addAll(it) }
+            try {
+                convertJsonToCredentialOption(credentialHint, autofillId)
+                        .let { credentialOptions.addAll(it) }
+            } catch (e: JSONException) {
+                Log.i(TAG, "Exception while parsing response: " + e.message)
+            }
         }
         return credentialOptions
     }
@@ -178,7 +285,8 @@
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
 
         val json = JSONObject(jsonString)
-        val options = json.getJSONArray(CRED_OPTIONS_KEY)
+        val jsonGet = json.getJSONObject(REQ_TYPE_KEY)
+        val options = jsonGet.getJSONArray(CRED_OPTIONS_KEY)
         for (i in 0 until options.length()) {
             val option = options.getJSONObject(i)
             val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY))
@@ -206,4 +314,15 @@
         }
         return result
     }
+
+    private fun isSameCredentialEntry(
+            info1: CredentialEntryInfo,
+            info2: CredentialEntryInfo
+    ): Boolean {
+        return info1.providerId == info2.providerId &&
+                info1.lastUsedTimeMillis == info2.lastUsedTimeMillis &&
+                info1.credentialType == info2.credentialType &&
+                info1.displayName == info2.displayName &&
+                info1.userName == info2.userName
+    }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 716f474..447a9d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -203,9 +203,14 @@
     UNLOCKED_AUTH_ENTRIES_ONLY,
 }
 
-// IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
-// entry exists
-private fun toProviderDisplayInfo(
+
+/**
+ * IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
+ * entry exists
+ *
+ * @hide
+ */
+fun toProviderDisplayInfo(
     providerInfoList: List<ProviderInfo>
 ): ProviderDisplayInfo {
     val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp
index e5f5cc2..c883b1f2 100644
--- a/packages/CredentialManager/wear/Android.bp
+++ b/packages/CredentialManager/wear/Android.bp
@@ -22,6 +22,7 @@
 
     static_libs: [
         "CredentialManagerShared",
+        "hilt_android",
         "Horologist",
         "PlatformComposeCore",
         "androidx.activity_activity-compose",
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 273d0b1..0a63cb7 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -29,13 +29,13 @@
 import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.belowTimeTextPreview
+import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.coroutines.launch
 
-class CredentialSelectorActivity : ComponentActivity() {
+@AndroidEntryPoint(ComponentActivity::class)
+class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
 
-    private val viewModel: CredentialSelectorViewModel by viewModels {
-        CredentialSelectorViewModel.Factory
-    }
+    private val viewModel: CredentialSelectorViewModel by viewModels()
 
     @OptIn(ExperimentalHorologistApi::class)
     override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
index e8e4033..6bd166e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
@@ -17,18 +17,7 @@
 package com.android.credentialmanager
 
 import android.app.Application
-import com.android.credentialmanager.di.inject
-import com.android.credentialmanager.repository.PasswordRepository
-import com.android.credentialmanager.repository.RequestRepository
+import dagger.hilt.android.HiltAndroidApp
 
-class CredentialSelectorApp : Application() {
-
-    lateinit var requestRepository: RequestRepository
-    lateinit var passwordRepository: PasswordRepository
-
-    override fun onCreate() {
-        super.onCreate()
-
-        inject()
-    }
-}
\ No newline at end of file
+@HiltAndroidApp(Application::class)
+class CredentialSelectorApp : Hilt_CredentialSelectorApp()
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index d557dc0..435cd37 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -18,20 +18,20 @@
 
 import android.content.Intent
 import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
 import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.CreationExtras
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.repository.RequestRepository
 import com.android.credentialmanager.ui.mappers.toGet
+import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import javax.inject.Inject
 
-class CredentialSelectorViewModel(
+@HiltViewModel
+class CredentialSelectorViewModel @Inject constructor(
     private val requestRepository: RequestRepository,
 ) : ViewModel() {
 
@@ -56,22 +56,6 @@
             requestRepository.processRequest(intent = intent, previousIntent = previousIntent)
         }
     }
-
-    companion object {
-        val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
-            @Suppress("UNCHECKED_CAST")
-            override fun <T : ViewModel> create(
-                modelClass: Class<T>,
-                extras: CreationExtras
-            ): T {
-                val application = checkNotNull(extras[APPLICATION_KEY])
-
-                return CredentialSelectorViewModel(
-                    requestRepository = (application as CredentialSelectorApp).requestRepository,
-                ) as T
-            }
-        }
-    }
 }
 
 sealed class CredentialSelectorUiState {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
new file mode 100644
index 0000000..cb1a4a1
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
@@ -0,0 +1,18 @@
+package com.android.credentialmanager.di
+
+import android.content.Context
+import android.content.pm.PackageManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+@Module
+@InstallIn(SingletonComponent::class)
+internal object AppModule {
+    @Provides
+    @JvmStatic
+    fun providePackageManager(@ApplicationContext context: Context): PackageManager =
+            context.packageManager
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
deleted file mode 100644
index 1e8f83d..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.credentialmanager.di
-
-import android.app.Application
-import com.android.credentialmanager.CredentialSelectorApp
-import com.android.credentialmanager.repository.PasswordRepository
-import com.android.credentialmanager.repository.RequestRepository
-
-// TODO b/301601582 add Hilt for dependency injection
-
-fun CredentialSelectorApp.inject() {
-    requestRepository = requestRepository(application = this)
-    passwordRepository = passwordRepository()
-}
-
-private fun requestRepository(
-    application: Application,
-): RequestRepository = RequestRepository(
-    application = application,
-)
-
-private fun passwordRepository(): PasswordRepository = PasswordRepository()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index c87cfd3..81a0672 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -47,8 +47,7 @@
     columnState: ScalingLazyColumnState,
     onCloseApp: () -> Unit,
     modifier: Modifier = Modifier,
-    viewModel: SinglePasswordScreenViewModel =
-        viewModel(factory = SinglePasswordScreenViewModel.Factory),
+    viewModel: SinglePasswordScreenViewModel = viewModel(),
 ) {
     viewModel.initialize()
 
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 3167e67..43514a0 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
@@ -21,11 +21,7 @@
 import androidx.activity.result.IntentSenderRequest
 import androidx.annotation.MainThread
 import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
 import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.CreationExtras
-import com.android.credentialmanager.CredentialSelectorApp
 import com.android.credentialmanager.TAG
 import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.model.Password
@@ -33,12 +29,15 @@
 import com.android.credentialmanager.repository.PasswordRepository
 import com.android.credentialmanager.repository.RequestRepository
 import com.android.credentialmanager.ui.model.PasswordUiModel
+import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
+import javax.inject.Inject
 
-class SinglePasswordScreenViewModel(
+@HiltViewModel
+class SinglePasswordScreenViewModel @Inject constructor(
     private val requestRepository: RequestRepository,
     private val passwordRepository: PasswordRepository,
 ) : ViewModel() {
@@ -105,23 +104,6 @@
             _uiState.value = SinglePasswordScreenUiState.Completed
         }
     }
-
-    companion object {
-        val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
-            @Suppress("UNCHECKED_CAST")
-            override fun <T : ViewModel> create(
-                modelClass: Class<T>,
-                extras: CreationExtras
-            ): T {
-                val application = checkNotNull(extras[APPLICATION_KEY])
-
-                return SinglePasswordScreenViewModel(
-                    requestRepository = (application as CredentialSelectorApp).requestRepository,
-                    passwordRepository = application.passwordRepository,
-                ) as T
-            }
-        }
-    }
 }
 
 sealed class SinglePasswordScreenUiState {
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4c313b2..3409c29 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.externalstorage;
 
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
@@ -64,7 +66,19 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.regex.Pattern;
 
+/**
+ * Presents content of the shared (a.k.a. "external") storage.
+ * <p>
+ * Starting with Android 11 (R), restricts access to the certain sections of the shared storage:
+ * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in
+ * the DocumentsUI by default.
+ * See <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ * Storage updates in Android 11</a>.
+ * <p>
+ * Documents ID format: {@code root:path/to/file}.
+ */
 public class ExternalStorageProvider extends FileSystemProvider {
     private static final String TAG = "ExternalStorage";
 
@@ -75,7 +89,12 @@
     private static final Uri BASE_URI =
             new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
 
-    // docId format: root:path/to/file
+    /**
+     * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
+     * {@code /Android/sandbox/} along with all their subdirectories and content.
+     */
+    private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
+            Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -278,76 +297,91 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
+    /**
+     * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the
+     * integrated shared ("external") storage along with all their content and subdirectories as
+     * hidden.
+     */
     @Override
-    public Cursor queryChildDocumentsForManage(
-            String parentDocId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+    protected boolean shouldHideDocument(@NonNull String documentId) {
+        // Don't need to hide anything on USB drives.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        final String path = getPathFromDocId(documentId);
+        return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
     }
 
     /**
      * Check that the directory is the root of storage or blocked file from tree.
+     * <p>
+     * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
+     * the UI, but the user <b>WILL NOT</b> be able to select them.
      *
-     * @param docId the docId of the directory to be checked
+     * @param documentId the docId of the directory to be checked
      * @return true, should be blocked from tree. Otherwise, false.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
      */
     @Override
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
-        try {
-            final File dir = getFileForDocId(docId, false /* visible */);
-
-            // the file is null or it is not a directory
-            if (dir == null || !dir.isDirectory()) {
-                return false;
-            }
-
-            // Allow all directories on USB, including the root.
-            try {
-                RootInfo rootInfo = getRootFromDocId(docId);
-                if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
-                    return false;
-                }
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Failed to determine rootInfo for docId");
-            }
-
-            final String path = getPathFromDocId(docId);
-
-            // Block the root of the storage
-            if (path.isEmpty()) {
-                return true;
-            }
-
-            // Block Download folder from tree
-            if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android
-            if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs
-            if (shouldHide(dir)) {
-                return true;
-            }
-
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
+        final File dir = getFileForDocId(documentId, false);
+        // The file is null or it is not a directory
+        if (dir == null || !dir.isDirectory()) {
             return false;
-        } catch (IOException e) {
-            throw new IllegalArgumentException(
-                    "Failed to determine if " + docId + " should block from tree " + ": " + e);
         }
+
+        // Allow all directories on USB, including the root.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        // Get canonical(!) path. Note that this path will have neither leading nor training "/".
+        // This the root's path will be just an empty string.
+        final String path = getPathFromDocId(documentId);
+
+        // Block the root of the storage
+        if (path.isEmpty()) {
+            return true;
+        }
+
+        // Block /Download/ and /Android/ folders from the tree.
+        if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) ||
+                equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) {
+            return true;
+        }
+
+        // This shouldn't really make a difference, but just in case - let's block hidden
+        // directories as well.
+        if (shouldHideDocument(documentId)) {
+            return true;
+        }
+
+        return false;
     }
 
+    private boolean isOnRemovableUsbStorage(@NonNull String documentId) {
+        final RootInfo rootInfo;
+        try {
+            rootInfo = getRootFromDocId(documentId);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');
+            return false;
+        }
+
+        return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;
+    }
+
+    @NonNull
     @Override
-    protected String getDocIdForFile(File file) throws FileNotFoundException {
+    protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {
         return getDocIdForFileMaybeCreate(file, false);
     }
 
-    private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
+    @NonNull
+    private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)
             throws FileNotFoundException {
         String path = file.getAbsolutePath();
 
@@ -417,31 +451,33 @@
     private File getFileForDocId(String docId, boolean visible, boolean mustExist)
             throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return buildFile(root, docId, visible, mustExist);
+        return buildFile(root, docId, mustExist);
     }
 
-    private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
-            throws FileNotFoundException {
+    private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return Pair.create(root, buildFile(root, docId, visible, true));
+        return Pair.create(root, buildFile(root, docId, /* mustExist */ true));
     }
 
     @VisibleForTesting
-    static String getPathFromDocId(String docId) throws IOException {
+    static String getPathFromDocId(String docId) {
         final int splitIndex = docId.indexOf(':', 1);
         final String docIdPath = docId.substring(splitIndex + 1);
-        // Get CanonicalPath and remove the first "/"
-        final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1);
 
-        if (canonicalPath.isEmpty()) {
-            return canonicalPath;
+        // Canonicalize path and strip the leading "/"
+        final String path;
+        try {
+            path = new File(docIdPath).getCanonicalPath().substring(1);
+        } catch (IOException e) {
+            Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"');
+            return "";
         }
 
-        // remove trailing "/"
-        if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') {
-            return canonicalPath.substring(0, canonicalPath.length() - 1);
+        // Remove the trailing "/" as well.
+        if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') {
+            return path.substring(0, path.length() - 1);
         } else {
-            return canonicalPath;
+            return path;
         }
     }
 
@@ -460,7 +496,7 @@
         return root;
     }
 
-    private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
+    private File buildFile(RootInfo root, String docId, boolean mustExist)
             throws FileNotFoundException {
         final int splitIndex = docId.indexOf(':', 1);
         final String path = docId.substring(splitIndex + 1);
@@ -544,7 +580,7 @@
     @Override
     public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
             throws FileNotFoundException {
-        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);
         final RootInfo root = resolvedDocId.first;
         File child = resolvedDocId.second;
 
@@ -648,6 +684,13 @@
         }
     }
 
+    /**
+     * Print the state into the given stream.
+     * Gets invoked when you run:
+     * <pre>
+     * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider
+     * </pre>
+     */
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
@@ -731,4 +774,8 @@
         }
         return bundle;
     }
+
+    private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) {
+        return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT));
+    }
 }
diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
index 18a8edc..0144b6e 100644
--- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
+++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
@@ -16,47 +16,64 @@
 
 package com.android.externalstorage;
 
+import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
+
 import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY;
 import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.Instrumentation;
+import android.content.Context;
 import android.content.pm.ProviderInfo;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public class ExternalStorageProviderTest {
+
+    @NonNull
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+    @NonNull
+    private static final Context sTargetContext = sInstrumentation.getTargetContext();
+
+    private ExternalStorageProvider mExternalStorageProvider;
+
+    @Before
+    public void setUp() {
+        mExternalStorageProvider = new ExternalStorageProvider();
+    }
+
+
     @Test
-    public void onCreate_shouldUpdateVolumes() throws Exception {
-        ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider();
-        ExternalStorageProvider spyProvider = spy(externalStorageProvider);
-        ProviderInfo providerInfo = new ProviderInfo();
+    public void onCreate_shouldUpdateVolumes() {
+        final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider);
+
+        final ProviderInfo providerInfo = new ProviderInfo();
         providerInfo.authority = AUTHORITY;
         providerInfo.grantUriPermissions = true;
         providerInfo.exported = true;
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                spyProvider.attachInfoForTesting(
-                        InstrumentationRegistry.getTargetContext(), providerInfo);
-            }
-        });
+        sInstrumentation.runOnMainSync(() ->
+                spyProvider.attachInfoForTesting(sTargetContext, providerInfo));
 
         verify(spyProvider, atLeast(1)).updateVolumes();
     }
 
     @Test
-    public void testGetPathFromDocId() throws Exception {
+    public void test_getPathFromDocId() {
         final String root = "root";
         final String path = "abc/def/ghi";
         String docId = root + ":" + path;
@@ -79,4 +96,62 @@
         docId = root + ":" + twoDotPath;
         assertEquals(getPathFromDocId(docId), path);
     }
+
+    @Test
+    public void test_shouldHideDocument() {
+        // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their
+        // "subtrees".
+        final String[] shouldHide = {
+                // "Android/data" and all its subdirectories
+                "Android/data",
+                "Android/data/com.my.app",
+                "Android/data/com.my.app/cache",
+                "Android/data/com.my.app/cache/image.png",
+                "Android/data/mydata",
+
+                // "Android/obb" and all its subdirectories
+                "Android/obb",
+                "Android/obb/com.my.app",
+                "Android/obb/com.my.app/file.blob",
+
+                // "Android/sandbox" and all its subdirectories
+                "Android/sandbox",
+                "Android/sandbox/com.my.app",
+
+                // Also make sure we are not allowing path traversals
+                "Android/./data",
+                "Android/Download/../data",
+        };
+        for (String path : shouldHide) {
+            final String docId = buildDocId(path);
+            assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't",
+                    mExternalStorageProvider.shouldHideDocument(docId));
+        }
+
+        // Should NOT hide anything else.
+        final String[] shouldNotHide = {
+                "Android",
+                "Android/datadir",
+                "Documents",
+                "Download",
+                "Music",
+                "Pictures",
+        };
+        for (String path : shouldNotHide) {
+            final String docId = buildDocId(path);
+            assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did",
+                    mExternalStorageProvider.shouldHideDocument(docId));
+        }
+    }
+
+    @NonNull
+    private static String buildDocId(@NonNull String path) {
+        return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path);
+    }
+
+    @NonNull
+    private static String buildDocId(@NonNull String root, @NonNull String path) {
+        // docId format: root:path/to/file
+        return root + ':' + path;
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d97fb54..c5ae4a3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -381,7 +381,7 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            String resolvedPath = info.getResolvedBaseApkPath();
+            String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
             if (info == null || !info.isSealed() || resolvedPath == null) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
@@ -609,7 +609,7 @@
                 CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
                 if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label);
                 mAppSnippet = new PackageUtil.AppSnippet(label,
-                        mPm.getApplicationIcon(mPkgInfo.applicationInfo));
+                        mPm.getApplicationIcon(mPkgInfo.applicationInfo), getBaseContext());
             } break;
 
             case ContentResolver.SCHEME_FILE: {
@@ -647,7 +647,7 @@
         mPkgInfo = generateStubPackageInfo(info.getAppPackageName());
         mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(),
                 info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon())
-                        : getPackageManager().getDefaultActivityIcon());
+                        : getPackageManager().getDefaultActivityIcon(), getBaseContext());
         return true;
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 334886f..976a3ad 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -18,6 +18,7 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -135,15 +136,20 @@
     static final class AppSnippet implements Parcelable {
         @NonNull public CharSequence label;
         @Nullable public Drawable icon;
-        public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
+        public int iconSize;
+
+        AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) {
             this.label = label;
             this.icon = icon;
+            final ActivityManager am = context.getSystemService(ActivityManager.class);
+            this.iconSize = am.getLauncherLargeIconSize();
         }
 
         private AppSnippet(Parcel in) {
             label = in.readString();
             Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
             icon = new BitmapDrawable(Resources.getSystem(), bmp);
+            iconSize = in.readInt();
         }
 
         @Override
@@ -161,6 +167,7 @@
             dest.writeString(label.toString());
             Bitmap bmp = getBitmapFromDrawable(icon);
             dest.writeParcelable(bmp, 0);
+            dest.writeInt(iconSize);
         }
 
         private Bitmap getBitmapFromDrawable(Drawable drawable) {
@@ -174,6 +181,14 @@
             // bitmap held within
             drawable.draw(canvas);
 
+            // Scale it down if the icon is too large
+            if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) {
+                Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+                if (scaledBitmap != bmp) {
+                    bmp.recycle();
+                }
+                return scaledBitmap;
+            }
             return bmp;
         }
 
@@ -241,7 +256,7 @@
         } catch (OutOfMemoryError e) {
             Log.i(LOG_TAG, "Could not load app icon", e);
         }
-        return new PackageUtil.AppSnippet(label, icon);
+        return new PackageUtil.AppSnippet(label, icon, pContext);
     }
 
     private static String findFilePath(File[] files, String postfix) {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2d231f2..8964ada 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -11,29 +11,42 @@
     name: "SettingsLib",
 
     static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.appcompat_appcompat",
-        "androidx.coordinatorlayout_coordinatorlayout",
-        "androidx.core_core",
-        "androidx.fragment_fragment",
-        "androidx.lifecycle_lifecycle-runtime",
-        "androidx.loader_loader",
         "androidx.localbroadcastmanager_localbroadcastmanager",
-        "androidx.preference_preference",
-        "androidx.recyclerview_recyclerview",
-        "com.google.android.material_material",
-        "iconloader",
+        "androidx.room_room-runtime",
+        "zxing-core",
 
         "WifiTrackerLibRes",
+        "iconloader",
+        "setupdesign",
+
+        "SettingsLibActionBarShadow",
+        "SettingsLibActionButtonsPreference",
+        "SettingsLibAdaptiveIcon",
+        "SettingsLibAppPreference",
+        "SettingsLibBannerMessagePreference",
+        "SettingsLibBarChartPreference",
+        "SettingsLibButtonPreference",
+        "SettingsLibCollapsingToolbarBaseActivity",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibDisplayUtils",
         "SettingsLibEmergencyNumber",
+        "SettingsLibEntityHeaderWidgets",
+        "SettingsLibFooterPreference",
+        "SettingsLibHelpUtils",
+        "SettingsLibIllustrationPreference",
+        "SettingsLibLayoutPreference",
+        "SettingsLibMainSwitchPreference",
+        "SettingsLibProfileSelector",
+        "SettingsLibProgressBar",
+        "SettingsLibRestrictedLockUtils",
         "SettingsLibSearchWidget",
+        "SettingsLibSelectorWithWidgetPreference",
+        "SettingsLibSettingsSpinner",
+        "SettingsLibSettingsTransition",
+        "SettingsLibTopIntroPreference",
+        "SettingsLibTwoTargetPreference",
+        "SettingsLibUsageProgressBarPreference",
         "SettingsLibUtils",
-        "SettingsLibWidget",
-        "setupdesign",
-        "zxing-core-1.7",
-        "androidx.room_room-runtime",
         "settingslib_flags_lib",
     ],
 
@@ -47,56 +60,10 @@
     ],
 }
 
-// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to
-// set use_resource_processor = true.
-// We can remove SettingsLibWidget when all these libraries have its own namespace.
-android_library {
-    name: "SettingsLibWidget",
-    visibility: ["//visibility:private"],
-    manifest: "AndroidManifest-SettingsLibWidget.xml",
-    static_libs: [
-        "SettingsLibActionBarShadow",
-        "SettingsLibActionButtonsPreference",
-        "SettingsLibAdaptiveIcon",
-        "SettingsLibAppPreference",
-        "SettingsLibBannerMessagePreference",
-        "SettingsLibBarChartPreference",
-        "SettingsLibButtonPreference",
-        "SettingsLibCollapsingToolbarBaseActivity",
-        "SettingsLibEntityHeaderWidgets",
-        "SettingsLibFooterPreference",
-        "SettingsLibHelpUtils",
-        "SettingsLibIllustrationPreference",
-        "SettingsLibLayoutPreference",
-        "SettingsLibMainSwitchPreference",
-        "SettingsLibProfileSelector",
-        "SettingsLibProgressBar",
-        "SettingsLibRestrictedLockUtils",
-        "SettingsLibSelectorWithWidgetPreference",
-        "SettingsLibSettingsSpinner",
-        "SettingsLibSettingsTransition",
-        "SettingsLibTopIntroPreference",
-        "SettingsLibTwoTargetPreference",
-        "SettingsLibUsageProgressBarPreference",
-    ],
-
-    resource_dirs: [],
-}
-
 // NOTE: Keep this module in sync with ./common.mk
 java_defaults {
     name: "SettingsLibDefaults",
     static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.appcompat_appcompat",
-        "androidx.coordinatorlayout_coordinatorlayout",
-        "androidx.core_core",
-        "androidx.fragment_fragment",
-        "androidx.lifecycle_lifecycle-runtime",
-        "androidx.loader_loader",
-        "androidx.localbroadcastmanager_localbroadcastmanager",
-        "androidx.preference_preference",
-        "androidx.recyclerview_recyclerview",
         "SettingsLib",
     ],
 }
diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
deleted file mode 100644
index 38a7d6a..0000000
--- a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
+++ /dev/null
@@ -1,18 +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.
--->
-
-<manifest package="com.android.settingslib.widget" />
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 8975857..47ce587 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -83,7 +83,7 @@
             android:id="@android:id/progress"
             style="?android:attr/progressBarStyleHorizontal"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="4dp"
             android:layout_marginTop="4dp"
             android:max="100"
             android:visibility="gone"/>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 155cfbb..b633337 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -173,13 +173,12 @@
         return mCoordinatorLayout;
     }
 
-    /** Sets the title on the collapsing layout, delegating to host if needed. */
+    /** Sets the title on the collapsing layout and delegates to host. */
     public void setTitle(CharSequence title) {
         if (mCollapsingToolbarLayout != null) {
             mCollapsingToolbarLayout.setTitle(title);
-        } else {
-            mHostCallback.setOuterTitle(title);
         }
+        mHostCallback.setOuterTitle(title);
     }
 
     /** Returns an instance of collapsing toolbar. */
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 33aa985..4871ef3 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -9,6 +9,7 @@
 
 android_library {
     name: "SettingsLibMainSwitchPreference",
+    use_resource_processor: true,
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 56b3eac..6001155 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -31,12 +31,11 @@
 import androidx.annotation.ColorInt;
 
 import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.mainswitch.R;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import com.android.settingslib.widget.mainswitch.R;
-
 /**
  * MainSwitchBar is a View with a customized Switch.
  * This component is used as the main switch of the page
@@ -77,7 +76,7 @@
             final TypedArray a = context.obtainStyledAttributes(
                     new int[]{android.R.attr.colorAccent});
             mBackgroundActivatedColor = a.getColor(0, 0);
-            mBackgroundColor = context.getColor(R.color.material_grey_600);
+            mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600);
             a.recycle();
         }
 
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index f1e028b..6979825 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -25,6 +25,7 @@
         <item name="editTextPreferenceStyle">@style/SettingsEditTextPreference.SettingsLib</item>
         <item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference.SettingsLib</item>
         <item name="switchPreferenceStyle">@style/SettingsSwitchPreference.SettingsLib</item>
+        <item name="switchPreferenceCompatStyle">@style/SettingsSwitchPreferenceCompat.SettingsLib</item>
         <item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference.SettingsLib</item>
         <item name="footerPreferenceStyle">@style/Preference.Material</item>
     </style>
@@ -67,6 +68,11 @@
         <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
     </style>
 
+    <style name="SettingsSwitchPreferenceCompat.SettingsLib" parent="@style/Preference.SwitchPreferenceCompat.Material">
+        <item name="layout">@layout/settingslib_preference</item>
+        <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+    </style>
+
     <style name="SettingsSeekbarPreference.SettingsLib" parent="@style/Preference.SeekBarPreference.Material">
         <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
     </style>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index c4b6047..f44b161 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -34,12 +34,17 @@
     </style>
 
     <style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
-        <item name="android:switchMinWidth">52dp</item>
         <item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
         <item name="android:track">@drawable/settingslib_switch_track</item>
         <item name="android:thumb">@drawable/settingslib_switch_thumb</item>
     </style>
 
+    <style name="SwitchCompat.SettingsLib" parent="@style/Widget.AppCompat.CompoundButton.Switch">
+        <item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
+        <item name="track">@drawable/settingslib_switch_track</item>
+        <item name="android:thumb">@drawable/settingslib_switch_thumb</item>
+    </style>
+
     <style name="HorizontalProgressBar.SettingsLib"
            parent="android:style/Widget.Material.ProgressBar.Horizontal">
         <item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 69c122c..698f21d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -25,6 +25,7 @@
         <item name="android:listPreferredItemPaddingRight">16dp</item>
         <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
         <item name="android:switchStyle">@style/Switch.SettingsLib</item>
+        <item name="switchStyle">@style/SwitchCompat.SettingsLib</item>
         <item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
     </style>
 
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 1cc2867..0b7a568 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -26,7 +26,7 @@
 }
 
 allprojects {
-    extra["jetpackComposeVersion"] = "1.6.0-alpha02"
+    extra["jetpackComposeVersion"] = "1.6.0-alpha07"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 01596d2..d62b490 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -40,6 +40,7 @@
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
@@ -74,6 +75,7 @@
                 PreferencePageProvider,
                 SwitchPreferencePageProvider,
                 MainSwitchPreferencePageProvider,
+                ListPreferencePageProvider,
                 TwoTargetSwitchPreferencePageProvider,
                 ArgumentPageProvider,
                 SliderPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
similarity index 96%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index df1d7d1..b001cad 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -18,8 +18,8 @@
 
 import android.os.Bundle
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
 import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
@@ -47,7 +47,7 @@
     override fun Page(arguments: Bundle?) {
         RegularScaffold(title = TITLE) {
             val actionButtons = listOf(
-                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
                 ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
                 ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
             )
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
new file mode 100644
index 0000000..43b6d0b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.ListPreference
+import com.android.settingslib.spa.widget.preference.ListPreferenceModel
+import com.android.settingslib.spa.widget.preference.ListPreferenceOption
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flow
+
+private const val TITLE = "Sample ListPreference"
+
+object ListPreferencePageProvider : SettingsPageProvider {
+    override val name = "ListPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?) = listOf(
+        SettingsEntryBuilder.create("ListPreference", owner)
+            .setUiLayoutFn {
+                SampleListPreference()
+            }.build(),
+        SettingsEntryBuilder.create("ListPreference not changeable", owner)
+            .setUiLayoutFn {
+                SampleNotChangeableListPreference()
+            }.build(),
+    )
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    override fun getTitle(arguments: Bundle?) = TITLE
+}
+
+@Composable
+private fun SampleListPreference() {
+    val selectedId = rememberSaveable { mutableIntStateOf(1) }
+    ListPreference(remember {
+        object : ListPreferenceModel {
+            override val title = "Preferred network type"
+            override val options = listOf(
+                ListPreferenceOption(id = 1, text = "5G (recommended)"),
+                ListPreferenceOption(id = 2, text = "LTE"),
+                ListPreferenceOption(id = 3, text = "3G"),
+            )
+            override val selectedId = selectedId
+            override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+        }
+    })
+}
+
+@Composable
+private fun SampleNotChangeableListPreference() {
+    val selectedId = rememberSaveable { mutableIntStateOf(1) }
+    val enableFlow = flow {
+        var enabled = true
+        while (true) {
+            delay(3.seconds)
+            enabled = !enabled
+            emit(enabled)
+        }
+    }
+    val enabled = enableFlow.collectAsStateWithLifecycle(initialValue = true)
+    ListPreference(remember {
+        object : ListPreferenceModel {
+            override val title = "Preferred network type"
+            override val enabled = enabled
+            override val options = listOf(
+                ListPreferenceOption(id = 1, text = "5G (recommended)"),
+                ListPreferenceOption(id = 2, text = "LTE"),
+                ListPreferenceOption(id = 3, text = "3G"),
+            )
+            override val selectedId = selectedId
+            override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+        }
+    })
+}
+
+@Preview
+@Composable
+private fun ListPreferencePagePreview() {
+    SettingsTheme {
+        ListPreferencePageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index eddede7..ce9678b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -36,6 +36,7 @@
             PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
                 .setLink(fromPage = owner).build(),
         )
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 8b56336..aafae5f 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
 #
 
 [versions]
-agp = "8.1.1"
+agp = "8.1.2"
 compose-compiler = "1.5.1"
 dexmaker-mockito = "2.28.3"
 kotlin = "1.9.0"
-truth = "1.1"
+truth = "1.1.5"
 
 [libraries]
 dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index da04f42..ce89de6 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index b810511..b73bbd8 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-alpha04")
+    api("androidx.compose.material3:material3:1.2.0-alpha09")
     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.1")
+    api("androidx.navigation:navigation-compose:2.7.4")
     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/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 7962e60..4088ffd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -40,8 +40,18 @@
     /** The size when app icon is displayed in App info page. */
     val appIconInfoSize = 48.dp
 
+    /** The vertical padding for buttons. */
+    val buttonPaddingVertical = 12.dp
+
     /** The [PaddingValues] for buttons. */
-    val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = 12.dp)
+    val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = buttonPaddingVertical)
+
+    /** The horizontal padding for dialog items. */
+    val dialogItemPaddingHorizontal = itemPaddingStart
+
+    /** The [PaddingValues] for dialog items. */
+    val dialogItemPadding =
+        PaddingValues(horizontal = dialogItemPaddingHorizontal, vertical = buttonPaddingVertical)
 
     /** The sizes info of illustration widget. */
     val illustrationMaxWidth = 412.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index c8faef6..a9cd0e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -16,10 +16,15 @@
 
 package com.android.settingslib.spa.framework.theme
 
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+
 object SettingsOpacity {
     const val Full = 1f
     const val Disabled = 0.38f
     const val Divider = 0.2f
     const val SurfaceTone = 0.14f
     const val Hint = 0.9f
+
+    fun Modifier.alphaForEnabled(enabled: Boolean) = alpha(if (enabled) Full else Disabled)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index c66e20a..8c862d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -22,5 +22,5 @@
 object SettingsShape {
     val CornerMedium = RoundedCornerShape(12.dp)
 
-    val CornerLarge = RoundedCornerShape(24.dp)
+    val CornerExtraLarge = RoundedCornerShape(28.dp)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
index 90c44b5..330755c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
@@ -30,11 +30,11 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
-import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 @Composable
-internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
+internal fun EntryHighlight(content: @Composable () -> Unit) {
     val entryData = LocalEntryDataProvider.current
     val entryIsHighlighted = rememberSaveable { entryData.isHighlighted }
     var localHighlighted by rememberSaveable { mutableStateOf(false) }
@@ -45,15 +45,16 @@
     val backgroundColor by animateColorAsState(
         targetValue = when {
             localHighlighted -> MaterialTheme.colorScheme.surfaceVariant
-            else -> SettingsTheme.colorScheme.background
+            else -> Color.Transparent
         },
         animationSpec = repeatable(
             iterations = 3,
             animation = tween(durationMillis = 500),
             repeatMode = RepeatMode.Restart
-        )
+        ),
+        label = "BackgroundColorAnimation",
     )
     Box(modifier = Modifier.background(color = backgroundColor)) {
-        UiLayoutFn()
+        content()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 0552c40..979cf3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.FilledTonalButton
@@ -52,6 +51,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsShape
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.divider
+import androidx.compose.material.icons.automirrored.outlined.Launch
 
 data class ActionButton(
     val text: String,
@@ -65,7 +65,7 @@
     Row(
         Modifier
             .padding(SettingsDimension.buttonPadding)
-            .clip(SettingsShape.CornerLarge)
+            .clip(SettingsShape.CornerExtraLarge)
             .height(IntrinsicSize.Min)
     ) {
         for ((index, actionButton) in actionButtons.withIndex()) {
@@ -101,7 +101,9 @@
                 modifier = Modifier.size(SettingsDimension.itemIconSize),
             )
             Box(
-                modifier = Modifier.padding(top = 4.dp).fillMaxHeight(),
+                modifier = Modifier
+                    .padding(top = 4.dp)
+                    .fillMaxHeight(),
                 contentAlignment = Alignment.Center,
             ) {
                 Text(
@@ -129,7 +131,7 @@
     SettingsTheme {
         ActionButtons(
             listOf(
-                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
                 ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
                 ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
             )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
new file mode 100644
index 0000000..8b172da
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.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.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.Dialog
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+@Composable
+fun SettingsDialog(
+    title: String,
+    onDismissRequest: () -> Unit,
+    content: @Composable () -> Unit,
+) {
+    Dialog(onDismissRequest = onDismissRequest) {
+        Card(shape = SettingsShape.CornerExtraLarge) {
+            Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
+                Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
+                    SettingsTitle(title = title, useMediumWeight = true)
+                }
+                content()
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 6330ddf..4d42fba 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -29,13 +29,12 @@
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 
@@ -57,8 +56,7 @@
             .padding(end = paddingEnd),
         verticalAlignment = Alignment.CenterVertically,
     ) {
-        val alphaModifier =
-            Modifier.alpha(if (enabled.value) SettingsOpacity.Full else SettingsOpacity.Disabled)
+        val alphaModifier = Modifier.alphaForEnabled(enabled.value)
         BaseIcon(icon, alphaModifier, paddingStart)
         Titles(
             title = title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
new file mode 100644
index 0000000..19779f6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.RadioButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.IntState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.dialog.SettingsDialog
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+
+data class ListPreferenceOption(
+    val id: Int,
+    val text: String,
+)
+
+/**
+ * The widget model for [ListPreference] widget.
+ */
+interface ListPreferenceModel {
+    /**
+     * The title of this [ListPreference].
+     */
+    val title: String
+
+    /**
+     * The icon of this [ListPreference].
+     *
+     * Default is `null` which means no icon.
+     */
+    val icon: (@Composable () -> Unit)?
+        get() = null
+
+    /**
+     * Indicates whether this [ListPreference] is enabled.
+     *
+     * Disabled [ListPreference] will be displayed in disabled style.
+     */
+    val enabled: State<Boolean>
+        get() = stateOf(true)
+
+    val options: List<ListPreferenceOption>
+
+    val selectedId: IntState
+
+    val onIdSelected: (id: Int) -> Unit
+}
+
+@Composable
+fun ListPreference(model: ListPreferenceModel) {
+    var dialogOpened by rememberSaveable { mutableStateOf(false) }
+    if (dialogOpened) {
+        SettingsDialog(
+            title = model.title,
+            onDismissRequest = { dialogOpened = false },
+        ) {
+            Column(modifier = Modifier.selectableGroup()) {
+                for (option in model.options) {
+                    Radio(option, model.selectedId, model.enabled) {
+                        dialogOpened = false
+                        model.onIdSelected(it)
+                    }
+                }
+            }
+        }
+    }
+    Preference(model = remember(model) {
+        object : PreferenceModel {
+            override val title = model.title
+            override val summary = derivedStateOf {
+                model.options.find { it.id == model.selectedId.intValue }?.text ?: ""
+            }
+            override val icon = model.icon
+            override val enabled = model.enabled
+            override val onClick = { dialogOpened = true }.takeIf { model.options.isNotEmpty() }
+        }
+    })
+}
+
+@Composable
+private fun Radio(
+    option: ListPreferenceOption,
+    selectedId: IntState,
+    enabledState: State<Boolean>,
+    onIdSelected: (id: Int) -> Unit,
+) {
+    val selected = option.id == selectedId.intValue
+    val enabled = enabledState.value
+    Row(
+        modifier = Modifier
+            .fillMaxWidth()
+            .selectable(
+                selected = selected,
+                enabled = enabled,
+                onClick = { onIdSelected(option.id) },
+                role = Role.RadioButton,
+            )
+            .padding(SettingsDimension.dialogItemPadding),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        RadioButton(selected = selected, onClick = null, enabled = enabled)
+        Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd))
+        SettingsDialogItem(text = option.text, enabled = enabled)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index 3e04b16..0c16c8b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -39,7 +39,7 @@
                 true -> MaterialTheme.colorScheme.primaryContainer
                 else -> MaterialTheme.colorScheme.secondaryContainer
             },
-            shape = SettingsShape.CornerLarge,
+            shape = SettingsShape.CornerExtraLarge,
         ) {
             InternalSwitchPreference(
                 title = model.title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 62189dc..5f320f7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -18,18 +18,13 @@
 
 import androidx.appcompat.R
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 import androidx.compose.material.icons.outlined.Clear
 import androidx.compose.material.icons.outlined.FindInPage
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.LayoutDirection
 import com.android.settingslib.spa.framework.compose.LocalNavController
 
 /** Action that navigates back to last page. */
@@ -53,9 +48,8 @@
 private fun BackAction(contentDescription: String, onClick: () -> Unit) {
     IconButton(onClick) {
         Icon(
-            imageVector = Icons.Outlined.ArrowBack,
+            imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
             contentDescription = contentDescription,
-            modifier = Modifier.autoMirrored(),
         )
     }
 }
@@ -81,10 +75,3 @@
         )
     }
 }
-
-private fun Modifier.autoMirrored() = composed {
-    when (LocalLayoutDirection.current) {
-        LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f)
-        else -> this
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index aa148b0..9f7f040 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -21,7 +21,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
-import androidx.compose.material3.TabRow
+import androidx.compose.material3.PrimaryTabRow
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
@@ -43,7 +43,7 @@
         val coroutineScope = rememberCoroutineScope()
         val pagerState = rememberPagerState { titles.size }
 
-        TabRow(
+        PrimaryTabRow(
             selectedTabIndex = pagerState.currentPage,
             modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
             containerColor = Color.Transparent,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 57319e7..7f1acff 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.toMediumWeight
 
@@ -48,6 +49,17 @@
 }
 
 @Composable
+fun SettingsDialogItem(text: String, enabled: Boolean = true) {
+    Text(
+        text = text,
+        modifier = Modifier.alphaForEnabled(enabled),
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.bodyLarge,
+        overflow = TextOverflow.Ellipsis,
+    )
+}
+
+@Composable
 fun SettingsBody(
     body: String,
     maxLines: Int = Int.MAX_VALUE,
@@ -82,6 +94,9 @@
 private fun BasePreferencePreview() {
     SettingsTheme {
         Column(Modifier.width(100.dp)) {
+            SettingsTitle(
+                title = "Title",
+            )
             SettingsBody(
                 body = "Long long long long long long text",
             )
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index f59b0de..8d9bac6 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,8 +17,8 @@
 package com.android.settingslib.spa.widget.button
 
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
 import androidx.compose.material.icons.outlined.Close
-import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -43,7 +43,10 @@
         composeTestRule.setContent {
             ActionButtons(
                 listOf(
-                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(
+                        text = "Open",
+                        imageVector = Icons.AutoMirrored.Outlined.Launch
+                    ) {},
                 )
             )
         }
@@ -57,7 +60,7 @@
         composeTestRule.setContent {
             ActionButtons(
                 listOf(
-                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {
+                    ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {
                         clicked = true
                     },
                 )
@@ -74,7 +77,10 @@
         composeTestRule.setContent {
             ActionButtons(
                 listOf(
-                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(
+                        text = "Open",
+                        imageVector = Icons.AutoMirrored.Outlined.Launch
+                    ) {},
                     ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
                 )
             )
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt
new file mode 100644
index 0000000..c7582b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.onDialogText
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDialogTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            SettingsDialog(title = TITLE, onDismissRequest = {}) {}
+        }
+
+        composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun text_displayed() {
+        composeTestRule.setContent {
+            SettingsDialog(title = "", onDismissRequest = {}) {
+                SettingsDialogItem(text = TEXT)
+            }
+        }
+
+        composeTestRule.onDialogText(TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val TEXT = "Text"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
new file mode 100644
index 0000000..997a023
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.onDialogText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ListPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = emptyList<ListPreferenceOption>()
+                    override val selectedId = mutableIntStateOf(0)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun summary_showSelectedText() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+                    override val selectedId = mutableIntStateOf(1)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText("A").assertIsDisplayed()
+    }
+
+    @Test
+    fun click_optionsIsEmpty_notShowDialog() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = emptyList<ListPreferenceOption>()
+                    override val selectedId = mutableIntStateOf(0)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+    }
+
+    @Test
+    fun click_notEnabled_notShowDialog() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val enabled = stateOf(false)
+                    override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+                    override val selectedId = mutableIntStateOf(1)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+    }
+
+    @Test
+    fun click_optionsNotEmpty_showDialog() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+                    override val selectedId = mutableIntStateOf(1)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun select() {
+        val selectedId = mutableIntStateOf(1)
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = listOf(
+                        ListPreferenceOption(id = 1, text = "A"),
+                        ListPreferenceOption(id = 2, text = "B"),
+                    )
+                    override val selectedId = selectedId
+                    override val onIdSelected = { id: Int -> selectedId.intValue = id }
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+        composeTestRule.onDialogText("B").performClick()
+
+        composeTestRule.onNodeWithText("B").assertIsDisplayed()
+    }
+
+    @Test
+    fun select_dialogOpenThenDisable_itemAlsoDisabled() {
+        val selectedId = mutableIntStateOf(1)
+        val enabledState = mutableStateOf(true)
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val enabled = enabledState
+                    override val options = listOf(
+                        ListPreferenceOption(id = 1, text = "A"),
+                        ListPreferenceOption(id = 2, text = "B"),
+                    )
+                    override val selectedId = selectedId
+                    override val onIdSelected = { id: Int -> selectedId.intValue = id }
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+        enabledState.value = false
+
+        composeTestRule.onDialogText("B").assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+    }
+}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 50243dc..cce8235 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -41,7 +41,7 @@
     api("androidx.arch.core:core-testing:2.2.0-alpha01")
     api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-runtime-testing")
-    api("org.mockito.kotlin:mockito-kotlin:5.1.0")
+    api("org.mockito.kotlin:mockito-kotlin:2.2.11")
     api("org.mockito:mockito-core") {
         version {
             strictly("2.28.2")
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index eaeda3c..009407a 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -38,6 +38,7 @@
     static_libs: [
         "androidx.compose.runtime_runtime",
         "SpaPrivilegedLib",
+        "android.content.pm.flags-aconfig-java",
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
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 a428142..d95dd8c 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
@@ -17,6 +17,8 @@
 package com.android.settingslib.spaprivileged.model.app
 
 import android.content.Context
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
@@ -65,10 +67,15 @@
         AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId)
 }
 
-class AppListRepositoryImpl(private val context: Context) : AppListRepository {
+class AppListRepositoryImpl(
+    private val context: Context,
+    private val featureFlags: FeatureFlags
+) : AppListRepository {
     private val packageManager = context.packageManager
     private val userManager = context.userManager
 
+    constructor(context: Context) : this(context, FeatureFlagsImpl())
+
     override suspend fun loadApps(
         userId: Int,
         loadInstantApps: Boolean,
@@ -98,9 +105,13 @@
         userId: Int,
         matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> {
+        val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
+            PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+        val archivedPackagesFlag: Long = if (featureFlags.archiving())
+            PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
         val regularFlags = ApplicationInfoFlags.of(
-            (PackageManager.MATCH_DISABLED_COMPONENTS or
-                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+            disabledComponentsFlag or
+                archivedPackagesFlag
         )
         return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
             packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index dfd8f6b..3525037 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -18,8 +18,10 @@
 
 import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
+import android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER
 import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
 import android.content.Context
+import android.content.pm.UserInfo
 import com.android.settingslib.R
 
 class EnterpriseRepository(private val context: Context) {
@@ -30,8 +32,10 @@
     fun getEnterpriseString(updatableStringId: String, resId: Int): String =
         checkNotNull(resources.getString(updatableStringId) { context.getString(resId) })
 
-    fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) {
+    fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) {
         getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
+    } else if (userInfo.isPrivateProfile) {
+        getEnterpriseString(PRIVATE_CATEGORY_HEADER, R.string.category_private)
     } else {
         getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal)
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index f54de15..09cb98e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -22,6 +22,8 @@
 import android.os.UserManager
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -32,15 +34,15 @@
 import com.android.settingslib.widget.restricted.R
 
 data class Restrictions(
-    val userId: Int,
+    val userId: Int = UserHandle.myUserId(),
     val keys: List<String>,
 )
 
 sealed interface RestrictedMode
 
-object NoRestricted : RestrictedMode
+data object NoRestricted : RestrictedMode
 
-object BaseUserRestricted : RestrictedMode
+data object BaseUserRestricted : RestrictedMode
 
 interface BlockedByAdmin : RestrictedMode {
     fun getSummary(checked: Boolean?): String
@@ -79,6 +81,17 @@
 
 typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
 
+@Composable
+internal fun RestrictionsProviderFactory.rememberRestrictedMode(
+    restrictions: Restrictions,
+): State<RestrictedMode?> {
+    val context = LocalContext.current
+    val restrictionsProvider = remember(restrictions) {
+        this(context, restrictions)
+    }
+    return restrictionsProvider.restrictedModeState()
+}
+
 internal class RestrictionsProviderImpl(
     private val context: Context,
     private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 1fa854a..17e9708 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,6 +45,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.flow.Flow
 
@@ -149,14 +150,13 @@
 
     @Composable
     fun getSummary(record: T): State<String> {
-        val restrictionsProvider = remember(record.app.userId) {
-            val restrictions = Restrictions(
+        val restrictions = remember(record.app.userId) {
+            Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
             )
-            restrictionsProviderFactory(context, restrictions)
         }
-        val restrictedMode = restrictionsProvider.restrictedModeState()
+        val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)
         val allowed = listModel.isAllowed(record)
         return remember {
             derivedStateOf {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
index 6a76c93..5447f21 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -45,7 +45,7 @@
         val enterpriseRepository = EnterpriseRepository(context)
         userGroups.map { userGroup ->
             enterpriseRepository.getProfileTitle(
-                isManagedProfile = userGroup.userInfos.first().isManagedProfile,
+                userGroup.userInfos.first(),
             )
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
new file mode 100644
index 0000000..50490c0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+@Composable
+fun RestrictedPreference(
+    model: PreferenceModel,
+    restrictions: Restrictions,
+) {
+    RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedPreference(
+    model: PreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+    if (restrictions.keys.isEmpty()) {
+        Preference(model)
+        return
+    }
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+    val restrictedSwitchModel = remember(restrictedMode) {
+        RestrictedPreferenceModel(model, restrictedMode)
+    }
+    restrictedSwitchModel.RestrictionWrapper {
+        Preference(restrictedSwitchModel)
+    }
+}
+
+private class RestrictedPreferenceModel(
+    model: PreferenceModel,
+    private val restrictedMode: RestrictedMode?,
+) : PreferenceModel {
+    override val title = model.title
+    override val summary = model.summary
+    override val icon = model.icon
+
+    override val enabled = when (restrictedMode) {
+        NoRestricted -> model.enabled
+        else -> stateOf(false)
+    }
+
+    override val onClick = when (restrictedMode) {
+        NoRestricted -> model.onClick
+        // Need to passthrough onClick for clickable semantics, although since enabled is false so
+        // this will not be called.
+        BaseUserRestricted -> model.onClick
+        else -> null
+    }
+
+    @Composable
+    fun RestrictionWrapper(content: @Composable () -> Unit) {
+        if (restrictedMode !is BlockedByAdmin) {
+            content()
+            return
+        }
+        Box(
+            Modifier
+                .clickable(
+                    role = Role.Button,
+                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                )
+        ) { content() }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e77dcd4..2129403 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spaprivileged.template.preference
 
 import android.content.Context
+import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
@@ -40,22 +41,29 @@
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
 
 @Composable
 fun RestrictedSwitchPreference(
     model: SwitchPreferenceModel,
     restrictions: Restrictions,
-    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+) {
+    RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedSwitchPreference(
+    model: SwitchPreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     if (restrictions.keys.isEmpty()) {
         SwitchPreference(model)
         return
     }
     val context = LocalContext.current
-    val restrictionsProvider = remember(restrictions) {
-        restrictionsProviderFactory(context, restrictions)
-    }
-    val restrictedMode = restrictionsProvider.restrictedModeState().value
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
     val restrictedSwitchModel = remember(restrictedMode) {
         RestrictedSwitchPreferenceModel(context, model, restrictedMode)
     }
@@ -112,8 +120,8 @@
     override val onCheckedChange = when (restrictedMode) {
         null -> null
         is NoRestricted -> model.onCheckedChange
-        // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
-        // since changeable is false this will not be called.
+        // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+        // is false so this will not be called.
         is BaseUserRestricted -> model.onCheckedChange
         // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
         is BlockedByAdmin -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 86b6f02..f9abefc 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,15 +16,15 @@
 
 package com.android.settingslib.spaprivileged.template.scaffold
 
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
 import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
 
 @Composable
 fun MoreOptionsScope.RestrictedMenuItem(
@@ -35,6 +35,7 @@
     RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
 }
 
+@VisibleForTesting
 @Composable
 internal fun MoreOptionsScope.RestrictedMenuItemImpl(
     text: String,
@@ -42,12 +43,8 @@
     onClick: () -> Unit,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
-    val context = LocalContext.current
-    val restrictionsProvider = remember(restrictions) {
-        restrictionsProviderFactory(context, restrictions)
-    }
-    val restrictedMode = restrictionsProvider.restrictedModeState().value
-    MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+    MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
         when (restrictedMode) {
             is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
             else -> onClick()
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 517f67e..840bca8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -47,6 +47,8 @@
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
+import android.content.pm.FakeFeatureFlagsImpl
+import android.content.pm.Flags
 
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
@@ -268,6 +270,40 @@
     }
 
     @Test
+    fun loadApps_archivedAppsEnabled() = runTest {
+        val fakeFlags = FakeFeatureFlagsImpl()
+        fakeFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+        mockInstalledApplications(listOf(NORMAL_APP, ARCHIVED_APP), ADMIN_USER_ID)
+        val repository = AppListRepositoryImpl(context, fakeFlags)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP, ARCHIVED_APP)
+        argumentCaptor<ApplicationInfoFlags> {
+            verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
+            assertThat(firstValue.value).isEqualTo(
+                (PackageManager.MATCH_DISABLED_COMPONENTS or
+                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or
+                    PackageManager.MATCH_ARCHIVED_PACKAGES
+            )
+        }
+    }
+
+    @Test
+    fun loadApps_archivedAppsDisabled() = runTest {
+        mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+        argumentCaptor<ApplicationInfoFlags> {
+            verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
+            assertThat(firstValue.value).isEqualTo(
+                PackageManager.MATCH_DISABLED_COMPONENTS or
+                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+            )
+        }
+    }
+
+    @Test
     fun showSystemPredicate_showSystem() = runTest {
         val app = SYSTEM_APP
 
@@ -391,6 +427,12 @@
             flags = ApplicationInfo.FLAG_SYSTEM
         }
 
+        val ARCHIVED_APP = ApplicationInfo().apply {
+            packageName = "archived.app"
+            flags = ApplicationInfo.FLAG_SYSTEM
+            isArchived = true
+        }
+
         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
                 this.packageName = packageName
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
new file mode 100644
index 0000000..eadf0ca
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.template.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private var clicked = false
+
+    private val preferenceModel = object : PreferenceModel {
+        override val title = TITLE
+        override val onClick = { clicked = true }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenNoRestricted_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notClickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(clicked).isFalse()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        composeTestRule.setContent {
+            RestrictedPreference(preferenceModel, restrictions) { _, _ ->
+                fakeRestrictionsProvider
+            }
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index d1bcb57..4936f88 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -5,4 +5,11 @@
   namespace: "media_solutions"
   description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
   bug: "192657812"
+}
+
+flag {
+  name: "enable_tv_media_output_dialog"
+  namespace: "tv_system_ui"
+  description: "Gates all the changes for the tv specific media output dialog"
+  bug: "303205631"
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index d959656..431fd44 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -18,18 +18,5 @@
 # to the corresponding module.
 # NOTE: keep this file and ./Android.bp in sync.
 
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    androidx.annotation_annotation
-
 LOCAL_STATIC_ANDROID_LIBRARIES += \
-    androidx.appcompat_appcompat \
-    androidx.coordinatorlayout_coordinatorlayout \
-    androidx.core_core \
-    androidx.fragment_fragment \
-    androidx.lifecycle_lifecycle-runtime \
-    androidx.loader_loader \
-    androidx.localbroadcastmanager_localbroadcastmanager \
-    androidx.preference_preference \
-    androidx.recyclerview_recyclerview \
     SettingsLib
-
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 53db192..9d986f4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi twee stawe."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi drie stawe."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-sein vol."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Gekoppel aan jou toestel."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Oop netwerk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Veilige netwerk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-bedryfstelsel"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Kies profiel"</string>
     <string name="category_personal" msgid="6236798763159385225">"Persoonlik"</string>
     <string name="category_work" msgid="4014193632325996115">"Werk"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privaat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Kloon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaaropsies"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Aktiveer ontwikkelaaropsies"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 77c9a97..b4887b9 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ሁለት የWiFi አሞሌዎች።"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ሦስት የWiFi አሞሌዎች።"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"የWiFi ምልክት ሙሉ ነው።"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ከመሣሪያዎ ጋር ተገናኝቷል።"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"አውታረ መረብ ክፈት"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ደህንነቱ የተጠበቀ አውታረ መረብ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android  ስርዓተ ክወና"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"መገለጫ ይምረጡ"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"የገንቢዎች አማራጮችን አንቃ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 30d011a..b8f92a3 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏إشارة Wi-Fi تتكون من شريطين."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏إشارة Wi-Fi تتكون من ثلاثة أشرطة."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏إشارة Wi-Fi كاملة."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"تم الاتصال بجهازك."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبكة مفتوحة"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبكة محمية بكلمة مرور"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"‏نظام التشغيل Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"تفعيل خيارات المطورين"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 31aff6a..86b29c6 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ৱাই-ফাইৰ দুডাল দণ্ড।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ৱাই-ফাইৰ তিনিডাল দণ্ড।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ৱাই-ফাই সংকেত সৰ্বোচ্চ।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"আপোনাৰ ডিভাইচটোৰ সৈতে সংযোগ কৰা আছে।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"মুক্ত নেটৱৰ্ক"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"সুৰক্ষিত নেটৱৰ্ক"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"প্ৰ’ফাইল বাছনি কৰক"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"বিকাশকৰ্তা বিষয়ক বিকল্পসমূহ সক্ষম কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index c675ce7..6a08a25 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi iki xətdir."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi üç xətdir."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi siqnalı tamdır."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Cihaza qoşuldu."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Açıq şəbəkə"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Təhlükəsiz şəbəkə"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
     <string name="category_personal" msgid="6236798763159385225">"Şəxsi"</string>
     <string name="category_work" msgid="4014193632325996115">"İş"</string>
+    <string name="category_private" msgid="4244892185452788977">"Şəxsi"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klonlayın"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Developer seçimləri"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Developer variantlarını aktiv edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index f7be60f..ad829b9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi signal ima dve crte."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi signal ima tri crte."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi signal je najjači."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezano je sa uređajem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Bezbedna mreža"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Izaberite profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Lično"</string>
     <string name="category_work" msgid="4014193632325996115">"Posao"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klonirano"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index d2f2851..7cd748c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Два слупкi Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Тры слупкi Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Поўны сігнал Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Устаноўлена падключэнне да прылады."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Адкрытая сетка"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Бяспечная сетка"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"АС Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Выбраць профіль"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Уключыць параметры распрацоўшчыка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 2540c12..93fe481 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi е с две чертички."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi е с три чертички."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Сигналът за Wi-Fi е пълен."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Установена е връзка с устройството ви."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Защитена мрежа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android (ОС)"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Избор на потребителски профил"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Активиране на опциите за програмисти"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 745b944..efdf304 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ওয়াই ফাই এ দুইটি দণ্ড৷"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ওয়াই ফাই এ তিনটি দণ্ড৷"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ওয়াই ফাই এ সম্পূর্ণ সিগন্যাল৷"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"আপনার ডিভাইসের সাথে কানেক্ট করা হয়েছে।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"খোলা নেটওয়ার্ক"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"সুরক্ষিত নেটওয়ার্ক"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"প্রোফাইল বেছে নিন"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ডেভেলপার বিকল্প সক্ষম করুন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index cab0841..5d21dea 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi signal ima dvije crte."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi signal ima tri crte."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi signal je pun."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezani ste s uređajem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sigurna mreža"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Odaberite profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Lično"</string>
     <string name="category_work" msgid="4014193632325996115">"Posao"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klonirajte"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index a65b9c2..ea4a2fb 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Senyal Wi-Fi: dues barres."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Senyal Wi-Fi: tres barres."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Senyal Wi-Fi: complet."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"S\'ha connectat al teu dispositiu."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Xarxa oberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Xarxa segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Tria un perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Treball"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clona"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opcions per a desenvolupadors"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Activa les opcions per a desenvolupadors"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 96c02ae..648e8dd 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi – dvě čárky."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi – tři čárky."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi – plný signál."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Připojeno k vašemu zařízení."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Nezabezpečená síť"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Zabezpečená síť"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Vyberte profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Osobní"</string>
     <string name="category_work" msgid="4014193632325996115">"Pracovní"</string>
+    <string name="category_private" msgid="4244892185452788977">"Soukromé"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Pro vývojáře"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Aktivovat možnosti pro vývojáře"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 1e4c7b7..500bfc3 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi har to bjælker."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi har tre bjælker."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi har fuldt signal."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Der er oprettet forbindelse til din enhed."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åbent netværk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert netværk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Vælg profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
     <string name="category_work" msgid="4014193632325996115">"Arbejde"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Indstillinger for udviklere"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Aktivér indstillinger for udviklere"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 37f7e5a..df392f3 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WLAN: zwei Balken"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WLAN: drei Balken"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WLAN: volle Signalstärke"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Mit deinem Gerät verbunden."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Offenes Netzwerk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sicheres Netzwerk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,8 @@
     <string name="choose_profile" msgid="343803890897657450">"Profil auswählen"</string>
     <string name="category_personal" msgid="6236798763159385225">"Privat"</string>
     <string name="category_work" msgid="4014193632325996115">"Geschäftlich"</string>
+    <!-- no translation found for category_private (4244892185452788977) -->
+    <skip />
     <string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Entwickleroptionen"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Entwickleroptionen aktivieren"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 84b8bf2..a87c0d7 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Δύο γραμμές Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Τρεις γραμμές Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Άριστο σήμα Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Συνδέθηκε στη συσκευή σας."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ανοικτό δίκτυο"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Ασφαλές δίκτυο"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Λειτουργικό σύστημα Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 7298c02..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Work"</string>
+    <string name="category_private" msgid="4244892185452788977">"Private"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 46dd47d..11a39b2 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -216,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Work"</string>
+    <string name="category_private" msgid="4244892185452788977">"Private"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 7298c02..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Work"</string>
+    <string name="category_private" msgid="4244892185452788977">"Private"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 7298c02..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Work"</string>
+    <string name="category_private" msgid="4244892185452788977">"Private"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a7c327f76..8a32195 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -216,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎Choose profile‎‏‎‎‏‎"</string>
     <string name="category_personal" msgid="6236798763159385225">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎Personal‎‏‎‎‏‎"</string>
     <string name="category_work" msgid="4014193632325996115">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎Work‎‏‎‎‏‎"</string>
+    <string name="category_private" msgid="4244892185452788977">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎Private‎‏‎‎‏‎"</string>
     <string name="category_clone" msgid="1554511758987195974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎Clone‎‏‎‎‏‎"</string>
     <string name="development_settings_title" msgid="140296922921597393">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎Developer options‎‏‎‎‏‎"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎Enable developer options‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 86541fd..4276f59 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dos barras de Wi-Fi"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de Wi-Fi"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Señal de Wi-Fi excelente"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Se estableció conexión con el dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Red abierta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Red segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Elegir perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privado"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Activar opciones para programador"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index ef9cc03..8b7b211 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dos barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Señal de Wi-Fi al máximo."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado a tu dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Red abierta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Red segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Seleccionar perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privado"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Habilitar opciones para desarrolladores"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index c8d806b..9c46b6c 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi: kaks pulka."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi: kolm pulka."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi-signaal on tugev."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Seadmega ühendatud."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Avatud võrk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Turvaline võrk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profiili valimine"</string>
     <string name="category_personal" msgid="6236798763159385225">"Isiklik"</string>
     <string name="category_work" msgid="4014193632325996115">"Töö"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privaatne"</string>
     <string name="category_clone" msgid="1554511758987195974">"Kloonimine"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Arendaja valikud"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Arendaja valikute lubamine"</string>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 95adf96..00f43a3 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -264,7 +264,7 @@
   <string-array name="debug_hw_overdraw_entries">
     <item msgid="1968128556747588800">"Desaktibatuta"</item>
     <item msgid="3033215374382962216">"Erakutsi gainidatzi diren eremuak"</item>
-    <item msgid="3474333938380896988">"Erakutsi daltonismorako eremuak"</item>
+    <item msgid="3474333938380896988">"Erakutsi deuteranomaliarako eremuak"</item>
   </string-array>
   <string-array name="app_process_limit_entries">
     <item msgid="794656271086646068">"Muga estandarra"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 7d76514..4436d52 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi sarearen bi barra."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi sarearen hiru barra."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi sarearen seinalea osoa."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Gailura konektatuta."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Sare irekia"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sare segurua"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android sistema eragilea"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Aukeratu profila"</string>
     <string name="category_personal" msgid="6236798763159385225">"Pertsonalak"</string>
     <string name="category_work" msgid="4014193632325996115">"Lanekoak"</string>
+    <string name="category_private" msgid="4244892185452788977">"Pribatua"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klonatu"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Garatzaileentzako aukerak"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Gaitu garatzaileen aukerak"</string>
@@ -368,7 +368,7 @@
     <string name="debug_hw_overdraw" msgid="8944851091008756796">"Araztu GPU gainidazketa"</string>
     <string name="disable_overlays" msgid="4206590799671557143">"Desgaitu HW gainjartzeak"</string>
     <string name="disable_overlays_summary" msgid="1954852414363338166">"Erabili beti GPUa pantaila-muntaietarako"</string>
-    <string name="simulate_color_space" msgid="1206503300335835151">"Simulatu kolore-eremua"</string>
+    <string name="simulate_color_space" msgid="1206503300335835151">"Simulatu kolore-espazioa"</string>
     <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Gaitu OpenGL aztarnak"</string>
     <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Desgaitu USB bidez audioa bideratzeko aukera"</string>
     <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Desgaitu USB bidezko audio-gailuetara automatikoki bideratzeko aukera"</string>
@@ -441,7 +441,7 @@
     <string name="picture_color_mode_desc" msgid="151780973768136200">"Erabili sRGB"</string>
     <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Desgaituta"</string>
     <string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Ikusmen-monokromia"</string>
-    <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Daltonismoa (gorri-berdeak)"</string>
+    <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomalia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (urdin-horia)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index beb0b96..7ae6b7b 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏دو نوار برای Wi‑Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏سه نوار برای Wi‑Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏قدرت سیگنال Wi‑Fi کامل است."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"به دستگاهتان متصل شد."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبکه باز"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبکه ایمن"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"‏سیستم‌عامل Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"انتخاب نمایه"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینه‌های برنامه‌نویس"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 8872316..6822d08 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-signaali – kaksi palkkia"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-signaali – kolme palkkia"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Vahva Wi-Fi-signaali"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Yhdistetty laitteeseesi."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Avoin verkko"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Suojattu verkko"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-käyttöjärjestelmä"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
     <string name="category_personal" msgid="6236798763159385225">"Henkilökohtainen"</string>
     <string name="category_work" msgid="4014193632325996115">"Työ"</string>
+    <string name="category_private" msgid="4244892185452788977">"Yksityinen"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klooni"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Kehittäjäasetukset"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Ota kehittäjäasetukset käyttöön"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 98e3753..12bc78e 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi : deux barres."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi : trois barres."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi : signal complet."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connecté à votre appareil."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Réseau ouvert"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Réseau sécurisé"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Système d\'exploitation Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Sélectionnez un profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personnel"</string>
     <string name="category_work" msgid="4014193632325996115">"Professionnel"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privés"</string>
     <string name="category_clone" msgid="1554511758987195974">"Cloner"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 4a64041..527b473 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Signal Wi-Fi moyen"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Signal Wi-Fi bon"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Signal Wi-Fi excellent"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connecté à votre appareil."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Réseau ouvert"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Réseau sécurisé"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Perso"</string>
     <string name="category_work" msgid="4014193632325996115">"Pro"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privé"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 26738e6..00d7b6e 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dúas barras de wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal completo de wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conexión establecida co teu dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Escoller perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Persoal"</string>
     <string name="category_work" msgid="4014193632325996115">"Traballo"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privado"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opcións para programadores"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións para programadores"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index b620d41..b0819fb 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi બે બાર."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ત્રણ બાર."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"પૂર્ણ Wifi સિગ્નલ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"તમારા ડિવાઇસ સાથે કનેક્ટેડ છે."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"નેટવર્ક ખોલો"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"સુરક્ષિત નેટવર્ક"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"પ્રોફાઇલ પસંદ કરો"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"વિકાસકર્તાનાં વિકલ્પો સક્ષમ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 3811167..99572f3 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"वाई-फ़ाई की दो पट्टी मिल रही हैं."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"वाई-फ़ाई की एक पट्टी मिल रही है."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"पूरे वाई-फ़ाई सिग्नल मिल रहे हैं."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"आपके डिवाइस से कनेक्ट हो गया है."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"खुला नेटवर्क"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"प्रोफ़ाइल चुनें"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"डेवलपर के लिए सेटिंग और टूल चालू करें"</string>
@@ -598,7 +598,7 @@
     <string name="profile_info_settings_title" msgid="105699672534365099">"प्रोफ़ाइल की जानकारी"</string>
     <string name="user_need_lock_message" msgid="4311424336209509301">"इससे पहले कि आप कोई प्रतिबंधित प्रोफ़ाइल बनाएं, आपको अपने ऐप्लिकेशन  और व्यक्तिगत डेटा की सुरक्षा करने के लिए एक स्क्रीन लॉक सेट करना होगा."</string>
     <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करें"</string>
-    <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर जाएं"</string>
+    <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर स्विच करें"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नए उपयोगकर्ता को जोड़ा जा रहा है…"</string>
     <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"नया मेहमान खाता बनाया जा रहा है…"</string>
     <string name="add_user_failed" msgid="4809887794313944872">"नया उपयोगकर्ता जोड़ा नहीं जा सका"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 71a2dae..735d3e9 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi signal ima dva stupca."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi signal ima tri stupca."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal je pun."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezano s vašim uređajem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sigurna mreža"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Odabir profila"</string>
     <string name="category_personal" msgid="6236798763159385225">"Osobno"</string>
     <string name="category_work" msgid="4014193632325996115">"Posao"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
     <string name="category_clone" msgid="1554511758987195974">"Kloniranje"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opcije za razvojne programere"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za razvojne programere"</string>
@@ -619,7 +619,7 @@
     <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Time će se izbrisati aplikacije i podaci iz trenutačne gostujuće sesije."</string>
     <string name="grant_admin" msgid="4323199171790522574">"Da, dodijeli status administratora"</string>
     <string name="not_grant_admin" msgid="3557849576157702485">"Ne, nemoj dodijeliti status administratora"</string>
-    <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izlaz"</string>
+    <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izađi"</string>
     <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Želite li spremiti aktivnosti gosta?"</string>
     <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Možete spremiti aktivnosti iz ove sesije ili izbrisati sve aplikacije i podatke"</string>
     <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 5fac75d..70b7d581 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-jel: két sáv."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-jel: három sáv."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-jel: teljes."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Csatlakoztatva az eszközhöz."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Nyílt hálózat"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Biztonságos hálózat"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profil kiválasztása"</string>
     <string name="category_personal" msgid="6236798763159385225">"Személyes"</string>
     <string name="category_work" msgid="4014193632325996115">"Munkahelyi"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privát"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klónozás"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Fejlesztői beállítások"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Fejlesztői beállítások engedélyezése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index e40c5e1..9340a3b 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-ի ուժգնությունը՝ երկու գիծ:"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-ի ուժգնությունը՝ երեք գիծ:"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-ի ազդանշանը ուժեղ է:"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Միացած է ձեր սարքին։"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Բաց ցանց"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Անվտանգ ցանց"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Ընտրեք պրոֆիլ"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Միացնել մշակողի ընտրանքները"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index ca1b6be..8cb3dca 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi dua baris"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi tiga baris."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinyal Wi-Fi penuh."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Terhubung ke perangkat Anda."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Jaringan terbuka"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Jaringan aman"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Pribadi"</string>
     <string name="category_work" msgid="4014193632325996115">"Kerja"</string>
+    <string name="category_private" msgid="4244892185452788977">"Pribadi"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opsi developer"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Aktifkan opsi developer"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 1f2dcfb..2f8ee7e 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: Tvö strik."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: Þrjú strik."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Fullur Wi-Fi sendistyrkur."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Tengt við tækið þitt."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Opið net"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Öruggt net"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android stýrikerfið"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Veldu snið"</string>
     <string name="category_personal" msgid="6236798763159385225">"Persónulegt"</string>
     <string name="category_work" msgid="4014193632325996115">"Vinna"</string>
+    <string name="category_private" msgid="4244892185452788977">"Lokað"</string>
     <string name="category_clone" msgid="1554511758987195974">"Afrit"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Forritunarkostir"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Virkja valkosti þróunaraðila"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index e255c85..9fe9f30 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: due barre."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: tre barre."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Segnale Wi-Fi completo."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connessione al dispositivo effettuata."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rete aperta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rete protetta"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistema operativo Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Scegli profilo"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personale"</string>
     <string name="category_work" msgid="4014193632325996115">"Lavoro"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privato"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opzioni sviluppatore"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Attiva Opzioni sviluppatore"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index edcac10..56ce9649 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏שני פסים של Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏שלושה פסים של Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏אות Wi-Fi מלא."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"מחובר למכשיר שלך."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"רשת פתוחה"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"רשת מאובטחת"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"בחירת פרופיל"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"הפעלת אפשרויות למפתחים"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index bf21de2..50f57ef 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fiはレベル2です。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fiはレベル3です。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fiの電波はフルです。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"デバイスに接続しました。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"オープンネットワーク"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"保護されたネットワーク"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"プロファイルの選択"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"開発者向けオプションの有効化"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 9000de4..46e406d 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi სიგნალი ორ ზოლზეა."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi სიგნალი სამ ზოლზეა."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi სიგნალი სრულია."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"დაკავშირებულია თქვენს მოწყობილობასთან."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ღია ქსელი"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"დაცული ქსელი"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"აირჩიეთ პროფილი"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"დეველოპერთა პარამეტრების ჩართვა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 610fed1..42c8ca7 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi сигналы — екі жолақ."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi сигналы — үш жолақ."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi сигналы толық."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Құрылғыңызға жалғанды."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ашық желі"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Қауіпсіз желі"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Профильді таңдау"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Әзірлеуші ​​параметрлерін қосу"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index b7999c7..9503049 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi ពីរកាំ"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi បីកាំ"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"សេវា Wifi ពេញ"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"បានភ្ជាប់ទៅឧបករណ៍របស់អ្នក។"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"បើក​បណ្ដាញ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"បណ្តាញ​ដែល​មានសុវត្ថិភាព"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ប្រព័ន្ធ​ប្រតិបត្តិការ Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ជ្រើសរើស​កម្រងព័ត៌មាន"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"បើកដំណើរការជម្រើសអ្នកអភិវឌ្ឍន៍"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index c788dd5..f9ec91f 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ವೈಫೈ ಎರಡು ಪಟ್ಟಿಗಳು."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ವೈಫೈ ಮೂರು ಪಟ್ಟಿಗಳು."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ವೈಫೈ ಸಿಗ್ನಲ್‌‌ ಪೂರ್ತಿ ಇದೆ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ನೆಟ್‌ವರ್ಕ್‌ ತೆರೆಯಿರಿ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ಸುರಕ್ಷಿತ ನೆಟ್‌ವರ್ಕ್"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿ"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 2c51d1f..a180880 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 신호 막대가 두 개입니다."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 신호 막대가 세 개입니다."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 신호가 강합니다."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"기기에 연결되었습니다."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"개방형 네트워크"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"보안 네트워크"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"프로필 선택"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"개발자 옵션 사용"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 14814f4..b47356b 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: эки таякча."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: үч таякча."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi: күчтүү сигнал."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Түзмөгүңүзгө туташты."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ачык тармак"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Коопсуз тармак"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Профиль тандоо"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 6430a98..228eebe 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ສັນຍານ Wi-Fi ສອງຂີດ."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ສາມຂີດ."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ສັນຍານ Wi-Fi ເຕັມ"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ເຊື່ອມຕໍ່ກັບອຸປະກອນຂອງທ່ານແລ້ວ."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ເຄືອຂ່າຍເປີດ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ເຄືອຂ່າຍເຂົ້າລະຫັດ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ເລືອກໂປຣໄຟລ໌"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ເປີດໃຊ້ຕົວເລືອກນັກພັດທະນາ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 3642a90..5dfd2f4 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dvi „Wi-Fi“ signalo juostos."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Trys „Wi-Fi“ signalo juostos."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Stiprus „Wi-Fi“ signalas."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Prisijungta prie įrenginio."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Atviras tinklas"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Saugus tinklas"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"„Android“ OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profilio pasirinkimas"</string>
     <string name="category_personal" msgid="6236798763159385225">"Asmeninės"</string>
     <string name="category_work" msgid="4014193632325996115">"Darbo"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privatus"</string>
     <string name="category_clone" msgid="1554511758987195974">"Identiška kopija"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Kūrėjo parinktys"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Įgalinti kūrėjo parinktis"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index c3163b8..8b55355 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: divas joslas"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: trīs joslas"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Pilna piekļuve Wi-Fi signālam"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Izveidots savienojums ar ierīci."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Atvērts tīkls"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Drošs tīkls"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profila izvēlēšanās"</string>
     <string name="category_personal" msgid="6236798763159385225">"Privāts"</string>
     <string name="category_work" msgid="4014193632325996115">"Darba"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privāti"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klons"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Izstrādātāju opcijas"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Izstrādātāju opciju iespējošana"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 017fed7..72fea47 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Две црти на Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Три црти на Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Полн сигнал на Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Поврзано со уредот."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Заклучена мрежа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Оперативен систем Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Изберете профил"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Овозможете ги програмерските опции"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 3ef3f63..6308083f 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"വൈഫൈ സിഗ്നൽ രണ്ട് ബാറുകൾ."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"വൈഫൈ സിഗ്നൽ മൂന്ന് ബാറുകൾ."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"വൈഫൈ മികച്ച സിഗ്‌നൽ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"നിങ്ങളുടെ ഉപകരണത്തിലേക്ക് കണക്റ്റ് ചെയ്തു."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ഓപ്പൺ നെറ്റ്‍വര്‍ക്ക്"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"സുരക്ഷിത നെറ്റ്‍വര്‍ക്ക്"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ഡെവലപ്പർ ഓ‌പ്ഷനുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index ccd1d41..8707f40 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi сүлжээний дохио хоёр баганатай байна."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi сүлжээний дохио гурван баганатай байна."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-н дохио дүүрэн байна."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Таны төхөөрөмжид холбогдсон."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Нээлттэй сүлжээ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Аюулгүй сүлжээ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Андройд OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Профайл сонгох"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Хөгжүүлэгчийн сонголтыг идэвхжүүлэх"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index a34a9d3..ab8b88c 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"वाय-फाय दोन बार."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"वाय-फाय तीन बार."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"वाय-फाय सिग्नल संपूर्ण आहे."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"तुमच्या डिव्हाइसशी कनेक्ट केले आहे."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"नेटवर्क उघडा"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"प्रोफाइल निवडा"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"डेव्हलपर पर्याय सुरू करा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index b7d2734..2841c74 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi dua bar."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi tiga bar."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Isyarat Wi-Fi penuh."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Disambungkan kepada peranti anda."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rangkaian terbuka"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rangkaian selamat"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Peribadi"</string>
     <string name="category_work" msgid="4014193632325996115">"Tempat Kerja"</string>
+    <string name="category_private" msgid="4244892185452788977">"Peribadi"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Pilihan pembangun"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Dayakan pilihan pembangun"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 0e43f65..d915370 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi  ၂ ဘား"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi  ၃ ဘား"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi  အပြည့်ရှိ"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"သင့်စက်သို့ ချိတ်ဆက်ထားသည်။"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"အများသုံး ကွန်ရက်"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"လုံခြုံသည့် ကွန်ရက်"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ပရိုဖိုင်ကို ရွေးရန်"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ဆော့ဖ်ဝဲရေးသူအတွက် ရွေးစရာများကို ဖွင့်ပါ"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 43824c3..0a63c4b 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi-signal med to stolper."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi-signal med tre stolper."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-signalet er ved full styrke."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Koblet til enheten."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åpent nettverk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert nettverk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-operativsystem"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Velg profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
     <string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Utvikleralternativer"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Slå på utvikleralternativer"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index e879849..206444d 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi दुई पट्टि।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi तीन बारहरू।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"पूर्ण Wi-Fi सिंग्नल।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"तपाईंको डिभाइसमा कनेक्ट गरिएको छ।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"खुला नेटवर्क"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"प्रोफाइल रोज्नुहोस्"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"विकासकर्ता विकल्प सक्रिया गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index dedacff..af4797c9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: twee streepjes."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: drie streepjes."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifii-signaal is op volledige sterkte."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Verbonden met je apparaat."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open netwerk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Beveiligd netwerk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-besturingssysteem"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profiel kiezen"</string>
     <string name="category_personal" msgid="6236798763159385225">"Persoonlijk"</string>
     <string name="category_work" msgid="4014193632325996115">"Werk"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privé"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaarsopties"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Opties voor ontwikkelaars aanzetten"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index cbd9ffd..bbdafc8 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ୱାଇ-ଫାଇର ଦୁଇଟି ବାର୍‌ ଅଛି।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ୱାଇ-ଫାଇର ତିନୋଟି ବାର୍।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ୱାଇ-ଫାଇର ସଙ୍କେତ ସର୍ବୋଚ୍ଚ।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ଆପଣଙ୍କ ଡିଭାଇସ ସହ କନେକ୍ଟ କରାଯାଇଛି।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ଖୋଲା ନେଟୱର୍କ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ସୁରକ୍ଷିତ ନେଟ୍‌ୱର୍କ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ପ୍ରୋଫାଇଲ୍‌ ବାଛନ୍ତୁ"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ଡେଭଲପର୍‌ ବିକଳ୍ପଗୁଡ଼ିକ ସକ୍ଷମ କରନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index e4814ac..59eec15 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi ਦੋ ਬਾਰ।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ਤਿੰਨ ਬਾਰ।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi ਸਿਗਨਲ ਪੂਰਾ।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ਖੁੱਲ੍ਹਾ ਨੈੱਟਵਰਕ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ਸੁਰੱਖਿਅਤ ਨੈੱਟਵਰਕ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ਵਿਕਾਸਕਾਰ ਵਿਕਲਪਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f5dc9cc..d5d37f0 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: dwa paski."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: trzy paski."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: pełna moc sygnału."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Połączono z urządzeniem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Sieć otwarta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sieć zabezpieczona"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"System operacyjny Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Wybierz profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Osobiste"</string>
     <string name="category_work" msgid="4014193632325996115">"Służbowe"</string>
+    <string name="category_private" msgid="4244892185452788977">"Prywatne"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opcje programisty"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Włącz opcje programisty"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 4e96bc6..3b0010f28 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal Wi-Fi cheio."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado ao dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistema operacional Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
     <string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+    <string name="category_private" msgid="4244892185452788977">"Particular"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -232,7 +232,7 @@
     <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
     <string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
     <string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
-    <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+    <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
     <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 0c4abdb..c817f88 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal de Wi-Fi completo."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Com ligação ao dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
     <string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privado"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opções de programador"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Ativar as opções de programador"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 4e96bc6..3b0010f28 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal Wi-Fi cheio."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado ao dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistema operacional Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
     <string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+    <string name="category_private" msgid="4244892185452788977">"Particular"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -232,7 +232,7 @@
     <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
     <string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
     <string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
-    <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+    <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
     <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 529a6c6..5d855a5 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Semnal Wi-Fi: două bare."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Semnal Wi-Fi: trei bare."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Semnal Wi-Fi: complet."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectată la dispozitiv."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rețea nesecurizată"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Securizează rețeaua"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistem de operare Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Alege un profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Serviciu"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clonează"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opțiuni pentru dezvoltatori"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Activează opțiunile pentru dezvoltatori"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 87a81ee..9dac1d3 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: два деления"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: три деления"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: надежный сигнал"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Подключено к точке доступа на вашем устройстве."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Открытая сеть"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Защищенная сеть"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ОС Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Выбор профиля"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Включить параметры для разработчиков"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 9015a42..4b36694 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi තීරු දෙකයි."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi තීරු තුනයි."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi සංඥාව පිරී ඇත."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ඔබේ උපාංගයට සම්බන්ධයි."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"විවෘත ජාලය"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ආරක්ෂිත ජාලය"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"පැතිකඩ තෝරන්න"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"සංවර්ධක විකල්ප සබල කිරීම"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 8c6bf4f..30cc9fd 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dve čiarky signálu Wi‑Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tri čiarky signálu Wi‑Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Plný signál Wi‑Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Pripojené k vášmu zariadeniu."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorená sieť"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Zabezpečená sieť"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Výber profilu"</string>
     <string name="category_personal" msgid="6236798763159385225">"Osobné"</string>
     <string name="category_work" msgid="4014193632325996115">"Pracovné"</string>
+    <string name="category_private" msgid="4244892185452788977">"Súkromné"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klonovanie"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Pre vývojárov"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Povolenie možností vývojára"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index cc19abe..127c00d 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dve črtici signala Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tri črtice signala Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Poln signal Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezava z napravo je vzpostavljena."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Odprto omrežje"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Varno omrežje"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Izbira profila"</string>
     <string name="category_personal" msgid="6236798763159385225">"Osebno"</string>
     <string name="category_work" msgid="4014193632325996115">"Delo"</string>
+    <string name="category_private" msgid="4244892185452788977">"Zasebno"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Možnosti za razvijalce"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Omogočanje možnosti za razvijalce"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 43a2c4c..302e915 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi ka dy vija."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: tre vija."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi ka sinjal të plotë."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Lidhur me pajisjen tënde"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rrjet i hapur"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rrjet i sigurt"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistemi operativ Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Zgjidh profilin"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personale"</string>
     <string name="category_work" msgid="4014193632325996115">"Punë"</string>
+    <string name="category_private" msgid="4244892185452788977">"Private"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klono"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Opsionet e zhvilluesit"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Aktivizo opsionet e zhvilluesit"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 00a98bc..4d4ae0b 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi сигнал има две црте."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi сигнал има три црте."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi сигнал је најјачи."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Повезано је са уређајем."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Безбедна мрежа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android ОС"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Изаберите профил"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Омогући опције за програмере"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 01b462d..8fc6af6 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: två staplar."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: tre staplar."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Full signalstyrka för wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Ansluten till enheten."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Öppet nätverk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Säkert nätverk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Operativsystemet Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Välj profil"</string>
     <string name="category_personal" msgid="6236798763159385225">"Privat"</string>
     <string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+    <string name="category_private" msgid="4244892185452788977">"Privat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Utvecklaralternativ"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Aktivera utvecklaralternativ"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 9453e5f..f6cb654 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Vipima mtandao viwili vya Wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Vipima mtandao vitatu vya Wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Nguvu kamili ya mtandao wa Wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Imeunganishwa kwenye kifaa chako."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Mtandao unaotumiwa na mtu yeyote"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Mtandao salama"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Mfumo wa Uendeshaji wa Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Chagua wasifu"</string>
     <string name="category_personal" msgid="6236798763159385225">"Binafsi"</string>
     <string name="category_work" msgid="4014193632325996115">"Kazini"</string>
+    <string name="category_private" msgid="4244892185452788977">"Faragha"</string>
     <string name="category_clone" msgid="1554511758987195974">"Kloni"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Chaguo za wasanidi"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Washa chaguo za wasanidi programu"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 836b4da..41788bf 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"வைஃபை சிக்னல்: இரண்டு கோடுகள்."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"வைஃபை சிக்னல்: மூன்று கோடுகள்."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"வைஃபை சிக்னல் முழுமையாக உள்ளது."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"உங்கள் சாதனத்துடன் இணைக்கப்பட்டது."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"கடவுச்சொல் தேவைப்படாத திறந்த நெட்வொர்க்"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"கடவுச்சொல் தேவைப்படும் பாதுகாப்பான நெட்வொர்க்"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"சுயவிவரத்தைத் தேர்வு செய்க"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"டெவெலப்பர் விருப்பங்களை இயக்கு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 34b8264..4c71251 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi సిగ్నల్ రెండు బార్‌లు ఉంది."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi సిగ్నల్ మూడు బార్‌లు ఉంది."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi సిగ్నల్ పూర్తిగా ఉంది."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"మీ పరికరానికి కనెక్ట్ చేయబడింది."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ఓపెన్ నెట్‌వర్క్"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"సురక్షిత నెట్‌వర్క్"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"ప్రొఫైల్‌ను ఎంచుకోండి"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"డెవలపర్ ఎంపికలను ప్రారంభించండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index da3a5c5..cb6291a 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"สัญญาณ Wi-Fi 2 ขีด"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"สัญญาณ Wi-Fi 3 ขีด"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"สัญญาณ Wi-Fi เต็ม"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"เชื่อมต่อกับอุปกรณ์แล้ว"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"เครือข่ายแบบเปิด"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"เครือข่ายที่ปลอดภัย"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ระบบปฏิบัติการ Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"เลือกโปรไฟล์"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอป"</string>
@@ -598,7 +598,7 @@
     <string name="profile_info_settings_title" msgid="105699672534365099">"ข้อมูลโปรไฟล์"</string>
     <string name="user_need_lock_message" msgid="4311424336209509301">"ก่อนที่คุณจะสามารถสร้างโปรไฟล์ที่ถูกจำกัดได้ คุณจะต้องตั้งค่าล็อกหน้าจอเพื่อปกป้องแอปและข้อมูลส่วนตัวของคุณ"</string>
     <string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string>
-    <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+    <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น<xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
     <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"กำลังสร้างผู้ใช้ชั่วคราวใหม่…"</string>
     <string name="add_user_failed" msgid="4809887794313944872">"สร้างผู้ใช้ใหม่ไม่ได้"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f6d2256..44d5de2 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"May dalawang bar ang Wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"May tatlong bar ang Wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Puno ang signal ng Wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Nakakonekta sa iyong device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Bukas na network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Ligtas na network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Pumili ng profile"</string>
     <string name="category_personal" msgid="6236798763159385225">"Personal"</string>
     <string name="category_work" msgid="4014193632325996115">"Trabaho"</string>
+    <string name="category_private" msgid="4244892185452788977">"Pribado"</string>
     <string name="category_clone" msgid="1554511758987195974">"I-clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Mga opsyon ng developer"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"I-enable ang mga opsyon ng developer"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index acd3d00e..e33afa1 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -141,7 +141,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"İptal"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Eşleme işlemi, bağlantı kurulduğunda kişilerinize ve çağrı geçmişine erişim izni verir."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya şifre anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya geçiş anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile iletişim kurulamıyor."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Eşleme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> tarafından reddedildi."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Bilgisayar"</string>
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Kablosuz sinyal gücü iki çubuk."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Kablosuz sinyal gücü üç çubuk."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Kablosuz sinyal gücü tam."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Cihazınıza bağlandı."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Açık ağ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Güvenli ağ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
     <string name="category_personal" msgid="6236798763159385225">"Kişisel"</string>
     <string name="category_work" msgid="4014193632325996115">"İş"</string>
+    <string name="category_private" msgid="4244892185452788977">"Gizli"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Geliştirici seçenekleri"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Geliştirici seçeneklerini etkinleştir"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index dd5898a..319caca 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Дві смужки сигналу Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Три смужки сигналу Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Максимальний сигнал Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Підключено до пристрою."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Відкрита мережа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Захищена мережа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ОС Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Вибрати профіль"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"Увімкнути параметри розробника"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 182bd04..10dacde 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏Wifi دو بارز۔"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏Wifi تین بارز۔"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏Wifi سگنل پورا ہے۔"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"آپ کے آلے سے منسلک ہے۔"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"اوپن نیٹ ورک"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"محفوظ نیٹ ورک"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"پروفائل منتخب کریں"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"ڈویلپر کے اختیارات فعال کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index b831a4b..0a85c28 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: ikkita ustun"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: uchta ustun"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: signal to‘liq"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Qurilmaga ulandi."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ochiq tarmoq"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Xavfsiz tarmoq"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Profilni tanlang"</string>
     <string name="category_personal" msgid="6236798763159385225">"Shaxsiy"</string>
     <string name="category_work" msgid="4014193632325996115">"Ish"</string>
+    <string name="category_private" msgid="4244892185452788977">"Yopiq"</string>
     <string name="category_clone" msgid="1554511758987195974">"Nusxalash"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Dasturchi sozlamalari"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Dasturchi sozlamalarini yoqish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 3e94593..3c34f4d 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Tín hiệu Wi-Fi hai vạch."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tín hiệu Wi-Fi ba vạch."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Tín hiệu Wi-Fi đủ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Đã kết nối với thiết bị của bạn."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Mạng mở"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Mạng bảo mật"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Hệ điều hành Android"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Chọn hồ sơ"</string>
     <string name="category_personal" msgid="6236798763159385225">"Cá nhân"</string>
     <string name="category_work" msgid="4014193632325996115">"Công việc"</string>
+    <string name="category_private" msgid="4244892185452788977">"Riêng tư"</string>
     <string name="category_clone" msgid="1554511758987195974">"Nhân bản"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Tùy chọn cho nhà phát triển"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Bật tùy chọn nhà phát triển"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 53389c7..d385e67 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WLAN 信号强度为两格。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WLAN 信号强度为三格。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WLAN 信号满格。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已连接到您的设备。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"开放网络"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全网络"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android 操作系统"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"选择个人资料"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"启用开发者选项"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index daedba6..adcb4d8 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 訊號兩格。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 訊號三格。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 訊號滿格。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已連接裝置。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"開放式網絡"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全網絡"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android 作業系統"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 94ebd98..fddef2b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 訊號強度兩格。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 訊號強度三格。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 訊號強度滿格。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已連上裝置。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"開放式網路"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全網路"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android 作業系統"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</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>
     <string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 92a4b81..82b7306 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Amabha amabili we-Wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Amabha amathathu we-Wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Isiginali ye-Wifi igcwele."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Ixhume kudivayisi yakho."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Vula inethiwekhi"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Inethiwekhi evikelekile"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"I-Android OS"</string>
@@ -217,6 +216,7 @@
     <string name="choose_profile" msgid="343803890897657450">"Khetha iphrofayela"</string>
     <string name="category_personal" msgid="6236798763159385225">"Okomuntu siqu"</string>
     <string name="category_work" msgid="4014193632325996115">"Umsebenzi"</string>
+    <string name="category_private" msgid="4244892185452788977">"Okuyimfihlo"</string>
     <string name="category_clone" msgid="1554511758987195974">"Yenza i-clone"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Izinketho Zonjiniyela"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Nika amandla izinketho zonjiniyela"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 49bd9d9..6eaabbb 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -518,6 +518,8 @@
     <string name="category_personal">Personal</string>
     <!-- Header for items under the work user [CHAR LIMIT=30] -->
     <string name="category_work">Work</string>
+    <!-- Header for items under the private profile user [CHAR LIMIT=30] -->
+    <string name="category_private">Private</string>
     <!-- Header for items under the clone user [CHAR LIMIT=30] -->
     <string name="category_clone">Clone</string>
 
@@ -1096,19 +1098,6 @@
     <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] -->
     <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left</string>
 
-    <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
-    <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string>
-    <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
-    <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shut down soon</string>
-    <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
-    <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shut down soon</string>
-    <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
-    <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string>
-    <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
-    <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string>
-    <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
-    <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string>
-
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
     <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index 363e20aa..7fbd35b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
-import android.widget.Switch;
+import android.widget.CompoundButton;
 
 import androidx.annotation.Keep;
 import androidx.annotation.Nullable;
@@ -35,7 +35,7 @@
  */
 public class PrimarySwitchPreference extends RestrictedPreference {
 
-    private Switch mSwitch;
+    private CompoundButton mSwitch;
     private boolean mChecked;
     private boolean mCheckedSet;
     private boolean mEnableSwitch = true;
@@ -65,7 +65,7 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-        mSwitch = (Switch) holder.findViewById(R.id.switchWidget);
+        mSwitch = (CompoundButton) holder.findViewById(R.id.switchWidget);
         if (mSwitch != null) {
             mSwitch.setOnClickListener(v -> {
                 if (mSwitch != null && !mSwitch.isEnabled()) {
@@ -153,7 +153,7 @@
         setSwitchEnabled(admin == null);
     }
 
-    public Switch getSwitch() {
+    public CompoundButton getSwitch() {
         return mSwitch;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 758f090..60321eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -36,18 +36,17 @@
 import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
+import androidx.preference.SwitchPreferenceCompat;
 
 import com.android.settingslib.utils.BuildCompatUtils;
 
 /**
- * Version of SwitchPreference that can be disabled by a device admin
+ * Version of SwitchPreferenceCompat that can be disabled by a device admin
  * using a user restriction.
  */
-public class RestrictedSwitchPreference extends SwitchPreference {
+public class RestrictedSwitchPreference extends SwitchPreferenceCompat {
     RestrictedPreferenceHelper mHelper;
     AppOpsManager mAppOpsManager;
     boolean mUseAdditionalSummary = false;
@@ -93,8 +92,7 @@
     }
 
     public RestrictedSwitchPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle,
-                android.R.attr.switchPreferenceStyle));
+        this(context, attrs, androidx.preference.R.attr.switchPreferenceCompatStyle);
     }
 
     public RestrictedSwitchPreference(Context context) {
@@ -113,7 +111,7 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-        final View switchView = holder.findViewById(android.R.id.switch_widget);
+        final View switchView = holder.findViewById(androidx.preference.R.id.switchWidget);
         if (switchView != null) {
             final View rootView = switchView.getRootView();
             rootView.setFilterTouchesWhenObscured(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 412a342..ce0772f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -54,6 +54,7 @@
 import com.android.internal.util.UserIcons;
 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.IconFactory;
+import com.android.launcher3.util.UserIconInfo;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.settingslib.utils.BuildCompatUtils;
@@ -67,8 +68,7 @@
     static final String STORAGE_MANAGER_ENABLED_PROPERTY =
             "ro.storage_manager.enabled";
 
-    @VisibleForTesting
-    static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
+    public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
             "incompatible_charger_warning_disabled";
 
     private static Signature[] sSystemSignature;
@@ -598,15 +598,25 @@
 
     /** Get the corresponding adaptive icon drawable. */
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
-        UserManager um = context.getSystemService(UserManager.class);
-        boolean isClone = um.getProfiles(user.getIdentifier()).stream()
-                .anyMatch(profile ->
-                        profile.isCloneProfile() && profile.id == user.getIdentifier());
+        int userType = UserIconInfo.TYPE_MAIN;
+        try {
+            UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
+                    user.getIdentifier());
+            if (ui != null) {
+                if (ui.isCloneProfile()) {
+                    userType = UserIconInfo.TYPE_CLONED;
+                } else if (ui.isManagedProfile()) {
+                    userType = UserIconInfo.TYPE_WORK;
+                }
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
             return iconFactory
                     .createBadgedIconBitmap(
                             icon,
-                            new IconOptions().setUser(user).setIsCloneProfile(isClone))
+                            new IconOptions().setUser(new UserIconInfo(user, userType)))
                     .newIcon(context);
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 660090d..1900575 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -19,14 +19,19 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
 import android.util.Log;
 
@@ -36,6 +41,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * HapClientProfile handles the Bluetooth HAP service client role.
@@ -118,7 +124,52 @@
     }
 
     /**
-     * Get hearing aid devices matching connection states{
+     * Registers a {@link BluetoothHapClient.Callback} that will be invoked during the
+     * operation of this profile.
+     *
+     * Repeated registration of the same <var>callback</var> object after the first call to this
+     * method will result with IllegalArgumentException being thrown, even when the
+     * <var>executor</var> is different. API caller would have to call
+     * {@link #unregisterCallback(BluetoothHapClient.Callback)} with the same callback object
+     * before registering it again.
+     *
+     * @param executor an {@link Executor} to execute given callback
+     * @param callback user implementation of the {@link BluetoothHapClient.Callback}
+     * @throws NullPointerException if a null executor, or callback is given, or
+     *  IllegalArgumentException if the same <var>callback</var> is already registered.
+     * @hide
+     */
+    public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull BluetoothHapClient.Callback callback) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
+            return;
+        }
+        mService.registerCallback(executor, callback);
+    }
+
+    /**
+     * Unregisters the specified {@link BluetoothHapClient.Callback}.
+     * <p>The same {@link BluetoothHapClient.Callback} object used when calling
+     * {@link #registerCallback(Executor, BluetoothHapClient.Callback)} must be used.
+     *
+     * <p>Callbacks are automatically unregistered when application process goes away
+     *
+     * @param callback user implementation of the {@link BluetoothHapClient.Callback}
+     * @throws NullPointerException when callback is null or IllegalArgumentException when no
+     *  callback is registered
+     * @hide
+     */
+    public void unregisterCallback(@NonNull BluetoothHapClient.Callback callback) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot unregister callback.");
+            return;
+        }
+        mService.unregisterCallback(callback);
+    }
+
+    /**
+     * Gets hearing aid devices matching connection states{
      * {@code BluetoothProfile.STATE_CONNECTED},
      * {@code BluetoothProfile.STATE_CONNECTING},
      * {@code BluetoothProfile.STATE_DISCONNECTING}}
@@ -133,7 +184,7 @@
     }
 
     /**
-     * Get hearing aid devices matching connection states{
+     * Gets hearing aid devices matching connection states{
      * {@code BluetoothProfile.STATE_DISCONNECTED},
      * {@code BluetoothProfile.STATE_CONNECTED},
      * {@code BluetoothProfile.STATE_CONNECTING},
@@ -222,6 +273,270 @@
         return mService.supportsWritablePresets(device);
     }
 
+
+    /**
+     * Gets the group identifier, which can be used in the group related part of the API.
+     *
+     * <p>Users are expected to get group identifier for each of the connected device to discover
+     * the device grouping. This allows them to make an informed decision which devices can be
+     * controlled by single group API call and which require individual device calls.
+     *
+     * <p>Note that some binaural HA devices may not support group operations, therefore are not
+     * considered a valid HAP group. In such case -1 is returned even if such device is a valid Le
+     * Audio Coordinated Set member.
+     *
+     * @param device is the device for which we want to get the hap group identifier
+     * @return valid group identifier or -1
+     * @hide
+     */
+    public int getHapGroup(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get hap group.");
+            return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+        }
+        return mService.getHapGroup(device);
+    }
+
+    /**
+     * Gets the currently active preset for a HA device.
+     *
+     * @param device is the device for which we want to set the active preset
+     * @return active preset index or {@link BluetoothHapClient#PRESET_INDEX_UNAVAILABLE} if the
+     *         device is not connected.
+     * @hide
+     */
+    public int getActivePresetIndex(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get active preset index.");
+            return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+        }
+        return mService.getActivePresetIndex(device);
+    }
+
+    /**
+     * Gets the currently active preset info for a remote device.
+     *
+     * @param device is the device for which we want to get the preset name
+     * @return currently active preset info if selected, null if preset info is not available for
+     *     the remote device
+     * @hide
+     */
+    @Nullable
+    public BluetoothHapPresetInfo getActivePresetInfo(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get active preset info.");
+            return null;
+        }
+        return mService.getActivePresetInfo(device);
+    }
+
+    /**
+     * Selects the currently active preset for a HA device
+     *
+     * <p>On success,
+     * {@link BluetoothHapClient.Callback#onPresetSelected(BluetoothDevice, int, int)} will be
+     * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure,
+     * {@link BluetoothHapClient.Callback#onPresetSelectionFailed(BluetoothDevice, int)} will be
+     * called.
+     *
+     * @param device is the device for which we want to set the active preset
+     * @param presetIndex is an index of one of the available presets
+     * @hide
+     */
+    public void selectPreset(@NonNull BluetoothDevice device, int presetIndex) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot select preset.");
+            return;
+        }
+        mService.selectPreset(device, presetIndex);
+    }
+
+
+    /**
+     * Selects the currently active preset for a Hearing Aid device group.
+     *
+     * <p>This group call may replace multiple device calls if those are part of the valid HAS
+     * group. Note that binaural HA devices may or may not support group.
+     *
+     * <p>On success,
+     * {@link BluetoothHapClient.Callback#onPresetSelected(BluetoothDevice, int, int)} will be
+     * called for each device within the group with reason code
+     * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure,
+     * {@link BluetoothHapClient.Callback#onPresetSelectionForGroupFailed(int, int)} will be
+     * called for the group.
+     *
+     * @param groupId is the device group identifier for which want to set the active preset
+     * @param presetIndex is an index of one of the available presets
+     * @hide
+     */
+    public void selectPresetForGroup(int groupId, int presetIndex) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot select preset for group.");
+            return;
+        }
+        mService.selectPresetForGroup(groupId, presetIndex);
+    }
+
+    /**
+     * Sets the next preset as a currently active preset for a HA device
+     *
+     * <p>Note that the meaning of 'next' is HA device implementation specific and does not
+     * necessarily mean a higher preset index.
+     *
+     * @param device is the device for which we want to set the active preset
+     * @hide
+     */
+    public void switchToNextPreset(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot switch to next preset.");
+            return;
+        }
+        mService.switchToNextPreset(device);
+    }
+
+
+    /**
+     * Sets the next preset as a currently active preset for a HA device group
+     *
+     * <p>Note that the meaning of 'next' is HA device implementation specific and does not
+     * necessarily mean a higher preset index.
+     *
+     * <p>This group call may replace multiple device calls if those are part of the valid HAS
+     * group. Note that binaural HA devices may or may not support group.
+     *
+     * @param groupId is the device group identifier for which want to set the active preset
+     * @hide
+     */
+    public void switchToNextPresetForGroup(int groupId) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot switch to next preset for group.");
+            return;
+        }
+        mService.switchToNextPresetForGroup(groupId);
+    }
+
+    /**
+     * Sets the previous preset as a currently active preset for a HA device.
+     *
+     * <p>Note that the meaning of 'previous' is HA device implementation specific and does not
+     * necessarily mean a lower preset index.
+     *
+     * @param device is the device for which we want to set the active preset
+     * @hide
+     */
+    public void switchToPreviousPreset(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot switch to previous preset.");
+            return;
+        }
+        mService.switchToPreviousPreset(device);
+    }
+
+
+    /**
+     * Sets the next preset as a currently active preset for a HA device group
+     *
+     * <p>Note that the meaning of 'next' is HA device implementation specific and does not
+     * necessarily mean a higher preset index.
+     *
+     * <p>This group call may replace multiple device calls if those are part of the valid HAS
+     * group. Note that binaural HA devices may or may not support group.
+     *
+     * @param groupId is the device group identifier for which want to set the active preset
+     * @hide
+     */
+    public void switchToPreviousPresetForGroup(int groupId) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot switch to previous preset for "
+                    + "group.");
+            return;
+        }
+        mService.switchToPreviousPresetForGroup(groupId);
+    }
+
+    /**
+     * Requests the preset info
+     *
+     * @param device is the device for which we want to get the preset name
+     * @param presetIndex is an index of one of the available presets
+     * @return preset info
+     * @hide
+     */
+    public BluetoothHapPresetInfo getPresetInfo(@NonNull BluetoothDevice device, int presetIndex) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get preset info.");
+            return null;
+        }
+        return mService.getPresetInfo(device, presetIndex);
+    }
+
+    /**
+     * Gets all preset info for a particular device
+     *
+     * @param device is the device for which we want to get all presets info
+     * @return a list of all known preset info
+     * @hide
+     */
+    @NonNull
+    public List<BluetoothHapPresetInfo> getAllPresetInfo(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get all preset info.");
+            return new ArrayList<>();
+        }
+        return mService.getAllPresetInfo(device);
+    }
+
+    /**
+     * Sets the preset name for a particular device
+     *
+     * <p>Note that the name length is restricted to 40 characters.
+     *
+     * <p>On success,
+     * {@link BluetoothHapClient.Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a
+     * new name will be called and reason code
+     * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure,
+     * {@link BluetoothHapClient.Callback#onSetPresetNameFailed(BluetoothDevice, int)} will be
+     * called.
+     *
+     * @param device is the device for which we want to get the preset name
+     * @param presetIndex is an index of one of the available presets
+     * @param name is a new name for a preset, maximum length is 40 characters
+     * @hide
+     */
+    public void setPresetName(@NonNull BluetoothDevice device, int presetIndex,
+            @NonNull String name) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot set preset name.");
+            return;
+        }
+        mService.setPresetName(device, presetIndex, name);
+    }
+
+    /**
+     * Sets the name for a hearing aid preset.
+     *
+     * <p>Note that the name length is restricted to 40 characters.
+     *
+     * <p>On success,
+     * {@link BluetoothHapClient.Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a
+     * new name will be called for each device within the group with reason code
+     * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure,
+     * {@link BluetoothHapClient.Callback#onSetPresetNameForGroupFailed(int, int)} will be invoked
+     *
+     * @param groupId is the device group identifier
+     * @param presetIndex is an index of one of the available presets
+     * @param name is a new name for a preset, maximum length is 40 characters
+     * @hide
+     */
+    public void setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot set preset name for group.");
+            return;
+        }
+        mService.setPresetNameForGroup(groupId, presetIndex, name);
+    }
+
+
     @Override
     public boolean accessProfileEnabled() {
         return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 57867be..f83e37b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -19,6 +19,9 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -28,10 +31,11 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import java.util.ArrayList;
 import java.util.List;
-
-import androidx.annotation.RequiresApi;
+import java.util.concurrent.Executor;
 
 /**
  * VolumeControlProfile handles Bluetooth Volume Control Controller role
@@ -102,6 +106,88 @@
                 BluetoothProfile.VOLUME_CONTROL);
     }
 
+
+    /**
+     * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the
+     * operation of this profile.
+     *
+     * Repeated registration of the same <var>callback</var> object will have no effect after
+     * the first call to this method, even when the <var>executor</var> is different. API caller
+     * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with
+     * the same callback object before registering it again.
+     *
+     * @param executor an {@link Executor} to execute given callback
+     * @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
+     * @throws IllegalArgumentException if a null executor or callback is given
+     */
+    public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull BluetoothVolumeControl.Callback callback) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
+            return;
+        }
+        mService.registerCallback(executor, callback);
+    }
+
+    /**
+     * Unregisters the specified {@link BluetoothVolumeControl.Callback}.
+     * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling
+     * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
+     *
+     * <p>Callbacks are automatically unregistered when application process goes away
+     *
+     * @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
+     * @throws IllegalArgumentException when callback is null or when no callback is registered
+     */
+    public void unregisterCallback(@NonNull BluetoothVolumeControl.Callback callback) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot unregister callback.");
+            return;
+        }
+        mService.unregisterCallback(callback);
+    }
+
+    /**
+     * Tells the remote device to set a volume offset to the absolute volume.
+     *
+     * @param device {@link BluetoothDevice} representing the remote device
+     * @param volumeOffset volume offset to be set on the remote device
+     */
+    public void setVolumeOffset(BluetoothDevice device,
+            @IntRange(from = -255, to = 255) int volumeOffset) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
+            return;
+        }
+        if (device == null) {
+            Log.w(TAG, "Device is null. Cannot set volume offset.");
+            return;
+        }
+        mService.setVolumeOffset(device, volumeOffset);
+    }
+
+    /**
+     * Provides information about the possibility to set volume offset on the remote device.
+     * If the remote device supports Volume Offset Control Service, it is automatically
+     * connected.
+     *
+     * @param device {@link BluetoothDevice} representing the remote device
+     * @return {@code true} if volume offset function is supported and available to use on the
+     *         remote device. When Bluetooth is off, the return value should always be
+     *         {@code false}.
+     */
+    public boolean isVolumeOffsetAvailable(BluetoothDevice device) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get is volume offset available.");
+            return false;
+        }
+        if (device == null) {
+            Log.w(TAG, "Device is null. Cannot get is volume offset available.");
+            return false;
+        }
+        return mService.isVolumeOffsetAvailable(device);
+    }
+
     @Override
     public boolean accessProfileEnabled() {
         return false;
@@ -113,12 +199,12 @@
     }
 
     /**
-     * Get VolumeControlProfile devices matching connection states{
+     * Gets VolumeControlProfile devices matching connection states{
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
      *
      * @return Matching device list
-     * @code BluetoothProfile.STATE_CONNECTED,
-     * @code BluetoothProfile.STATE_CONNECTING,
-     * @code BluetoothProfile.STATE_DISCONNECTING}
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (mService == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
index a5f69ff..f988837 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
@@ -18,7 +18,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.preference.PreferenceGroupAdapter
-import androidx.preference.SwitchPreference
+import androidx.preference.TwoStatePreference
 import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.jank.InteractionJankMonitor
 import java.util.concurrent.Executors
@@ -37,13 +37,16 @@
     const val MONITORED_ANIMATION_DURATION_MS = 300L
 
     /**
-     * Detects the jank when click on a SwitchPreference.
+     * Detects the jank when click on a TwoStatePreference.
      *
      * @param recyclerView the recyclerView contains the preference
      * @param preference the clicked preference
      */
     @JvmStatic
-    fun detectSwitchPreferenceClickJank(recyclerView: RecyclerView, preference: SwitchPreference) {
+    fun detectSwitchPreferenceClickJank(
+        recyclerView: RecyclerView,
+        preference: TwoStatePreference,
+    ) {
         val adapter = recyclerView.adapter as? PreferenceGroupAdapter ?: return
         val adapterPosition = adapter.getPreferenceAdapterPosition(preference)
         val viewHolder = recyclerView.findViewHolderForAdapterPosition(adapterPosition) ?: return
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
deleted file mode 100644
index a78440c..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
+++ /dev/null
@@ -1,36 +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.settingslib.deviceinfo;
-
-import android.content.Context;
-import android.os.UserManager;
-
-import com.android.settingslib.Utils;
-import com.android.settingslib.core.AbstractPreferenceController;
-
-public abstract class AbstractSimStatusImeiInfoPreferenceController
-        extends AbstractPreferenceController {
-    public AbstractSimStatusImeiInfoPreferenceController(Context context) {
-        super(context);
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return mContext.getSystemService(UserManager.class).isAdminUser()
-                && !Utils.isWifiOnly(mContext);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 7f1f3f6..2032328 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -110,7 +110,8 @@
     }
 
     /**
-     * Determine whether the device is plugged in wireless. */
+     * Determine whether the device is plugged in wireless.
+     */
     public boolean isPluggedInWireless() {
         return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
     }
@@ -185,6 +186,22 @@
         return status == BATTERY_STATUS_FULL || level >= 100;
     }
 
+    /**
+     * Whether or not the device is charged. Note that some devices never return 100% for battery
+     * level, so this allows either battery level or status to determine if the battery is charged.
+     *
+     * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of
+     *     {@link Intent.ACTION_BATTERY_CHANGED} intent
+     * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of
+     *     {@link Intent.ACTION_BATTERY_CHANGED} intent
+     * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of
+     *     {@link Intent.ACTION_BATTERY_CHANGED} intent
+     */
+    public static boolean isCharged(int status, int level, int scale) {
+        var batteryLevel = getBatteryLevel(level, scale);
+        return isCharged(status, batteryLevel);
+    }
+
     /** Gets the battery level from the intent. */
     public static int getBatteryLevel(Intent batteryChangedIntent) {
         if (batteryChangedIntent == null) {
@@ -193,6 +210,14 @@
         final int level =
                 batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN);
         final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+        return getBatteryLevel(level, scale);
+    }
+
+    /**
+     * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL
+     * and EXTRA_SCALE.
+     */
+    public static int getBatteryLevel(int level, int scale) {
         return scale == 0
                 ? BATTERY_LEVEL_UNKNOWN
                 : Math.round((level / (float) scale) * 100f);
@@ -253,11 +278,22 @@
      *
      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
      * @return {@code true} if the battery level is less or equal to {@link
-     *     SEVERE_LOW_BATTERY_THRESHOLD}
+     * SEVERE_LOW_BATTERY_THRESHOLD}
      */
     public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
-        int level = getBatteryLevel(batteryChangedIntent);
-        return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+        int batteryLevel = getBatteryLevel(batteryChangedIntent);
+        return isSevereLowBattery(batteryLevel);
+    }
+
+    /**
+     * Whether the battery is severe low or not.
+     *
+     * @param batteryLevel the value of battery level
+     * @return {@code true} if the battery level is less or equal to {@link
+     * SEVERE_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isSevereLowBattery(int batteryLevel) {
+        return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD;
     }
 
     /**
@@ -265,11 +301,21 @@
      *
      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
      * @return {@code true} if the battery level is less or equal to {@link
-     *     EXTREME_LOW_BATTERY_THRESHOLD}
+     * EXTREME_LOW_BATTERY_THRESHOLD}
      */
     public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
         int level = getBatteryLevel(batteryChangedIntent);
-        return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+        return isExtremeLowBattery(level);
+    }
+
+    /**
+     * Whether the battery is extreme low or not.
+     *
+     * @return {@code true} if the {@code batteryLevel} is less or equal to
+     * {@link EXTREME_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isExtremeLowBattery(int batteryLevel) {
+        return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD;
     }
 
     /**
@@ -277,7 +323,7 @@
      *
      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
      * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
-     *     defend, or temp defend
+     * defend, or temp defend
      */
     public static boolean isBatteryDefender(Intent batteryChangedIntent) {
         int chargingStatus =
@@ -298,9 +344,8 @@
     }
 
     /**
-     * Gets the max charging current and max charging voltage form {@link
-     * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
-     * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+     * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold}
+     * and {@link R.integer.config_chargingFastThreshold}.
      *
      * @param context the application context
      * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
@@ -308,7 +353,29 @@
      *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
      */
     public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
-        final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+        final int maxChargingMicroCurrent =
+                batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+
+        return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt);
+    }
+
+    /**
+     * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold}
+     * and {@link R.integer.config_chargingFastThreshold}.
+     *
+     * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the
+     *     extra of {@link Intent.Action_BATTERY_CHANGED}
+     * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra
+     *     of {@link Intent.Action_BATTERY_CHANGED}
+     * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+     *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+     */
+    public static int calculateChargingSpeed(
+            Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) {
+        final int maxChargingMicroWatt =
+                calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt);
+
         if (maxChargingMicroWatt <= 0) {
             return CHARGING_UNKNOWN;
         } else if (maxChargingMicroWatt
@@ -326,6 +393,12 @@
         final int maxChargingMicroAmp =
                 batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
         int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+
+        return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt);
+    }
+
+    private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp,
+            int maxChargingMicroVolt) {
         if (maxChargingMicroVolt <= 0) {
             maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 2999c83..1501e27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -31,8 +31,8 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
+import android.widget.CompoundButton;
 import android.widget.ImageView;
-import android.widget.Switch;
 import android.widget.Toast;
 
 import androidx.preference.Preference;
@@ -138,7 +138,7 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-        final Switch switchWidget = getSwitch();
+        final CompoundButton switchWidget = getSwitch();
         if (switchWidget != null) {
             // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
             switchWidget.setOnClickListener(v -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index e38e041..2a28417 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -39,39 +39,50 @@
     @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
 
     public DeviceIconUtil() {
-        List<Device> deviceList = Arrays.asList(
-                new Device(
-                    AudioDeviceInfo.TYPE_USB_DEVICE,
-                    MediaRoute2Info.TYPE_USB_DEVICE,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_USB_HEADSET,
-                    MediaRoute2Info.TYPE_USB_HEADSET,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_USB_ACCESSORY,
-                    MediaRoute2Info.TYPE_USB_ACCESSORY,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_DOCK,
-                    MediaRoute2Info.TYPE_DOCK,
-                    R.drawable.ic_dock_device),
-                new Device(
-                    AudioDeviceInfo.TYPE_HDMI,
-                    MediaRoute2Info.TYPE_HDMI,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_WIRED_HEADSET,
-                    MediaRoute2Info.TYPE_WIRED_HEADSET,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
-                    MediaRoute2Info.TYPE_WIRED_HEADPHONES,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
-                    MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
-                    R.drawable.ic_smartphone));
+        List<Device> deviceList =
+                Arrays.asList(
+                        new Device(
+                                AudioDeviceInfo.TYPE_USB_DEVICE,
+                                MediaRoute2Info.TYPE_USB_DEVICE,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_USB_HEADSET,
+                                MediaRoute2Info.TYPE_USB_HEADSET,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_USB_ACCESSORY,
+                                MediaRoute2Info.TYPE_USB_ACCESSORY,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_DOCK,
+                                MediaRoute2Info.TYPE_DOCK,
+                                R.drawable.ic_dock_device),
+                        new Device(
+                                AudioDeviceInfo.TYPE_HDMI,
+                                MediaRoute2Info.TYPE_HDMI,
+                                R.drawable.ic_headphone),
+                        // TODO: b/306359110 - Put proper iconography for HDMI_ARC type.
+                        new Device(
+                                AudioDeviceInfo.TYPE_HDMI_ARC,
+                                MediaRoute2Info.TYPE_HDMI_ARC,
+                                R.drawable.ic_headphone),
+                        // TODO: b/306359110 - Put proper iconography for HDMI_EARC type.
+                        new Device(
+                                AudioDeviceInfo.TYPE_HDMI_EARC,
+                                MediaRoute2Info.TYPE_HDMI_EARC,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                                MediaRoute2Info.TYPE_WIRED_HEADSET,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                                MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                                MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+                                R.drawable.ic_smartphone));
         for (int i = 0; i < deviceList.size(); i++) {
             Device device = deviceList.get(i);
             mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bf63f5d..5dacba5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -21,6 +21,8 @@
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
@@ -635,6 +637,8 @@
             case TYPE_USB_ACCESSORY:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
                 mediaDevice =
@@ -668,11 +672,12 @@
                                 route,
                                 mPackageName,
                                 mPreferenceItemMap.get(route.getId()));
+                break;
             default:
                 Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
                 break;
-
         }
+
         if (mediaDevice != null && !TextUtils.isEmpty(mPackageName)
                 && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
             mediaDevice.setState(STATE_SELECTED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 147412d..8085c99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -21,6 +21,8 @@
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
@@ -140,7 +142,6 @@
             mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
             return;
         }
-
         switch (info.getType()) {
             case TYPE_GROUP:
                 mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
@@ -157,6 +158,8 @@
             case TYPE_USB_ACCESSORY:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 mType = MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE;
                 break;
             case TYPE_HEARING_AID:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 41afc7b..c44f66e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -18,6 +18,8 @@
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
 import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
@@ -26,6 +28,7 @@
 
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
@@ -51,6 +54,36 @@
 
     private final DeviceIconUtil mDeviceIconUtil;
 
+    /** Returns the device name for the given {@code routeInfo}. */
+    public static String getSystemRouteNameFromType(
+            @NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
+        CharSequence name;
+        switch (routeInfo.getType()) {
+            case TYPE_WIRED_HEADSET:
+            case TYPE_WIRED_HEADPHONES:
+            case TYPE_USB_DEVICE:
+            case TYPE_USB_HEADSET:
+            case TYPE_USB_ACCESSORY:
+                name = context.getString(R.string.media_transfer_wired_usb_device_name);
+                break;
+            case TYPE_DOCK:
+                name = context.getString(R.string.media_transfer_dock_speaker_device_name);
+                break;
+            case TYPE_BUILTIN_SPEAKER:
+                name = context.getString(R.string.media_transfer_this_device_name);
+                break;
+            case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
+                name = context.getString(R.string.media_transfer_external_device_name);
+                break;
+            default:
+                name = context.getString(R.string.media_transfer_default_device_name);
+                break;
+        }
+        return name.toString();
+    }
+
     PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) {
         this(context, info, packageName, null);
     }
@@ -69,29 +102,7 @@
     @SuppressWarnings("NewApi")
     @Override
     public String getName() {
-        CharSequence name;
-        switch (mRouteInfo.getType()) {
-            case TYPE_WIRED_HEADSET:
-            case TYPE_WIRED_HEADPHONES:
-            case TYPE_USB_DEVICE:
-            case TYPE_USB_HEADSET:
-            case TYPE_USB_ACCESSORY:
-                name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
-                break;
-            case TYPE_DOCK:
-                name = mContext.getString(R.string.media_transfer_dock_speaker_device_name);
-                break;
-            case TYPE_BUILTIN_SPEAKER:
-                name = mContext.getString(R.string.media_transfer_this_device_name);
-                break;
-            case TYPE_HDMI:
-                name = mContext.getString(R.string.media_transfer_external_device_name);
-                break;
-            default:
-                name = mContext.getString(R.string.media_transfer_default_device_name);
-                break;
-        }
-        return name.toString();
+        return getSystemRouteNameFromType(mContext, mRouteInfo);
     }
 
     @Override
@@ -137,6 +148,8 @@
             case TYPE_USB_ACCESSORY:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 id = USB_HEADSET_ID;
                 break;
             case TYPE_BUILTIN_SPEAKER:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 70956e9..9ab3b47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -38,6 +38,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
@@ -54,8 +55,11 @@
     private final RouteCallback mRouteCallback = new RouteCallback();
     private final TransferCallback mTransferCallback = new TransferCallback();
     private final ControllerCallback mControllerCallback = new ControllerCallback();
-    private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
-            new RouteListingPreferenceCallback();
+    private final Consumer<RouteListingPreference> mRouteListingPreferenceCallback =
+            (preference) -> {
+                notifyRouteListingPreferenceUpdated(preference);
+                refreshDevices();
+            };
 
     // TODO: b/192657812 - Create factory method in InfoMediaManager to return
     //      RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
@@ -83,7 +87,8 @@
     @Override
     protected void startScanOnRouter() {
         mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
-        mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
+        mRouter.registerRouteListingPreferenceUpdatedCallback(
+                mExecutor, mRouteListingPreferenceCallback);
         mRouter.registerTransferCallback(mExecutor, mTransferCallback);
         mRouter.registerControllerCallback(mExecutor, mControllerCallback);
         mRouter.startScan();
@@ -94,7 +99,7 @@
         mRouter.stopScan();
         mRouter.unregisterControllerCallback(mControllerCallback);
         mRouter.unregisterTransferCallback(mTransferCallback);
-        mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
+        mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback);
         mRouter.unregisterRouteCallback(mRouteCallback);
     }
 
@@ -308,13 +313,4 @@
             refreshDevices();
         }
     }
-
-    private final class RouteListingPreferenceCallback
-            extends MediaRouter2.RouteListingPreferenceCallback {
-        @Override
-        public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
-            notifyRouteListingPreferenceUpdated(preference);
-            refreshDevices();
-        }
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index 673f243..2272654 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -44,45 +44,6 @@
     private static final long ONE_MIN_MILLIS = TimeUnit.MINUTES.toMillis(1);
 
     /**
-     * This method produces the text used in various places throughout the system to describe the
-     * remaining battery life of the phone in a consistent manner.
-     *
-     * @param context
-     * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
-     * @param percentageString An optional percentage of battery remaining string.
-     * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
-     * @return a properly formatted and localized string describing how much time remains
-     * before the battery runs out.
-     */
-    public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
-            @Nullable String percentageString, boolean basedOnUsage) {
-        if (drainTimeMs > 0) {
-            if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
-                // show a imminent shutdown warning if less than 7 minutes remain
-                return getShutdownImminentString(context, percentageString);
-            } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
-                // show a less than 15 min remaining warning if appropriate
-                CharSequence timeString = StringUtil.formatElapsedTime(context,
-                        FIFTEEN_MINUTES_MILLIS,
-                        false /* withSeconds */, false /* collapseTimeUnit */);
-                return getUnderFifteenString(context, timeString, percentageString);
-            } else if (drainTimeMs >= TWO_DAYS_MILLIS) {
-                // just say more than two day if over 48 hours
-                return getMoreThanTwoDaysString(context, percentageString);
-            } else if (drainTimeMs >= ONE_DAY_MILLIS) {
-                // show remaining days & hours if more than a day
-                return getMoreThanOneDayString(context, drainTimeMs,
-                        percentageString, basedOnUsage);
-            } else {
-                // show the time of day we think you'll run out
-                return getRegularTimeRemainingString(context, drainTimeMs,
-                        percentageString, basedOnUsage);
-            }
-        }
-        return null;
-    }
-
-    /**
      * Method to produce a shortened string describing the remaining battery. Suitable for Quick
      * Settings and other areas where space is constrained.
      *
@@ -128,14 +89,6 @@
         }
     }
 
-    private static String getShutdownImminentString(Context context, String percentageString) {
-        return TextUtils.isEmpty(percentageString)
-                ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
-                : context.getString(
-                        R.string.power_remaining_duration_shutdown_imminent,
-                        percentageString);
-    }
-
     private static String getUnderFifteenString(Context context, CharSequence timeString,
             String percentageString) {
         return TextUtils.isEmpty(percentageString)
@@ -268,4 +221,4 @@
             return time - remainder + multiple;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
index d9cf9f2..debfa49 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
@@ -23,8 +23,8 @@
 
 import android.content.Context;
 import android.view.LayoutInflater;
+import android.widget.CompoundButton;
 import android.widget.LinearLayout;
-import android.widget.Switch;
 
 import androidx.preference.Preference.OnPreferenceChangeListener;
 import androidx.preference.PreferenceViewHolder;
@@ -62,7 +62,7 @@
 
     @Test
     public void setChecked_shouldUpdateButtonCheckedState() {
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         mPreference.onBindViewHolder(mHolder);
 
         mPreference.setChecked(true);
@@ -74,7 +74,7 @@
 
     @Test
     public void setSwitchEnabled_shouldUpdateButtonEnabledState() {
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         mPreference.onBindViewHolder(mHolder);
 
         mPreference.setSwitchEnabled(true);
@@ -86,7 +86,7 @@
 
     @Test
     public void setSwitchEnabled_shouldUpdateButtonEnabledState_beforeViewBound() {
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
 
         mPreference.setSwitchEnabled(false);
         mPreference.onBindViewHolder(mHolder);
@@ -97,7 +97,7 @@
     public void clickWidgetView_shouldToggleButton() {
         assertThat(mWidgetView).isNotNull();
 
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         mPreference.onBindViewHolder(mHolder);
 
         toggle.performClick();
@@ -111,7 +111,7 @@
     public void clickWidgetView_shouldNotToggleButtonIfDisabled() {
         assertThat(mWidgetView).isNotNull();
 
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         mPreference.onBindViewHolder(mHolder);
         toggle.setEnabled(false);
 
@@ -122,7 +122,7 @@
     @Test
     public void clickWidgetView_shouldNotifyPreferenceChanged() {
 
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
 
         final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
         mPreference.setOnPreferenceChangeListener(listener);
@@ -139,7 +139,7 @@
 
     @Test
     public void setDisabledByAdmin_hasEnforcedAdmin_shouldDisableButton() {
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         toggle.setEnabled(true);
         mPreference.onBindViewHolder(mHolder);
 
@@ -149,7 +149,7 @@
 
     @Test
     public void setDisabledByAdmin_noEnforcedAdmin_shouldEnableButton() {
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         toggle.setEnabled(false);
         mPreference.onBindViewHolder(mHolder);
 
@@ -159,7 +159,7 @@
 
     @Test
     public void onBindViewHolder_toggleButtonShouldHaveContentDescription() {
-        final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+        final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
         final String label = "TestButton";
         mPreference.setTitle(label);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
index 03a792a..7e3263b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
@@ -26,10 +26,12 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
@@ -47,11 +49,16 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class HapClientProfileTest {
 
+    private static final int TEST_GROUP_ID = 1;
+    private static final int TEST_PRESET_INDEX = 1;
+    private static final String TEST_DEVICE_NAME = "test_device";
+
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -63,6 +70,8 @@
     private BluetoothDevice mBluetoothDevice;
     @Mock
     private BluetoothHapClient mService;
+    @Mock
+    private BluetoothHapPresetInfo mPresetInfo;
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private BluetoothProfile.ServiceListener mServiceListener;
@@ -206,4 +215,275 @@
 
         assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
     }
+
+    /**
+     * Verify registerCallback() call is correctly delegated to {@link BluetoothHapClient} service.
+     */
+    @Test
+    public void registerCallback_verifyIsCalled() {
+        final Executor executor = (command -> new Thread(command).start());
+        final BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
+            @Override
+            public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex,
+                    int reason) {
+
+            }
+
+            @Override
+            public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
+
+            }
+
+            @Override
+            public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
+
+            }
+
+            @Override
+            public void onPresetInfoChanged(@NonNull BluetoothDevice device,
+                    @NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
+
+            }
+
+            @Override
+            public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
+
+            }
+
+            @Override
+            public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
+
+            }
+        };
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.registerCallback(executor, callback);
+
+        verify(mService).registerCallback(executor, callback);
+    }
+
+    /**
+     * Verify unregisterCallback() call is correctly delegated to {@link BluetoothHapClient}
+     * service.
+     */
+    @Test
+    public void unregisterCallback_verifyIsCalled() {
+        final BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
+            @Override
+            public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex,
+                    int reason) {
+
+            }
+
+            @Override
+            public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
+
+            }
+
+            @Override
+            public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
+
+            }
+
+            @Override
+            public void onPresetInfoChanged(@NonNull BluetoothDevice device,
+                    @NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
+
+            }
+
+            @Override
+            public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
+
+            }
+
+            @Override
+            public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
+
+            }
+        };
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.unregisterCallback(callback);
+
+        verify(mService).unregisterCallback(callback);
+    }
+
+    /**
+     * Verify getHapGroup() call is correctly delegated to {@link BluetoothHapClient} service
+     * and return correct value.
+     */
+    @Test
+    public void getHapGroup_verifyIsCalledAndReturnCorrectValue() {
+        when(mService.getHapGroup(mBluetoothDevice)).thenReturn(TEST_GROUP_ID);
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        final int groupId = mProfile.getHapGroup(mBluetoothDevice);
+
+        verify(mService).getHapGroup(mBluetoothDevice);
+        assertThat(groupId).isEqualTo(TEST_GROUP_ID);
+    }
+
+    /**
+     * Verify getActivePresetIndex() call is correctly delegated to {@link BluetoothHapClient}
+     * service and return correct index.
+     */
+    @Test
+    public void getActivePresetIndex_verifyIsCalledAndReturnCorrectValue() {
+        when(mService.getActivePresetIndex(mBluetoothDevice)).thenReturn(TEST_PRESET_INDEX);
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        final int activeIndex = mProfile.getActivePresetIndex(mBluetoothDevice);
+
+        verify(mService).getActivePresetIndex(mBluetoothDevice);
+        assertThat(activeIndex).isEqualTo(TEST_PRESET_INDEX);
+    }
+
+    /**
+     * Verify getActivePresetInfo() call is correctly delegated to {@link BluetoothHapClient}
+     * service and return correct object.
+     */
+    @Test
+    public void getActivePresetInfo_verifyIsCalledAndReturnCorrectObject() {
+        when(mService.getActivePresetInfo(mBluetoothDevice)).thenReturn(mPresetInfo);
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        final BluetoothHapPresetInfo activeInfo = mProfile.getActivePresetInfo(mBluetoothDevice);
+
+        verify(mService).getActivePresetInfo(mBluetoothDevice);
+        assertThat(activeInfo).isEqualTo(mPresetInfo);
+    }
+
+    /**
+     * Verify selectPreset() call is correctly delegated to {@link BluetoothHapClient} service.
+     */
+    @Test
+    public void selectPreset_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
+
+        verify(mService).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
+    }
+
+    /**
+     * Verify selectPresetForGroup() call is correctly delegated to {@link BluetoothHapClient}
+     * service.
+     */
+    @Test
+    public void selectPresetForGroup_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.selectPresetForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX);
+
+        verify(mService).selectPresetForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX);
+    }
+
+    /**
+     * Verify switchToNextPreset() call is correctly delegated to {@link BluetoothHapClient}
+     * service.
+     */
+    @Test
+    public void switchToNextPreset_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.switchToNextPreset(mBluetoothDevice);
+
+        verify(mService).switchToNextPreset(mBluetoothDevice);
+    }
+
+    /**
+     * Verify switchToNextPresetForGroup() call is correctly delegated to {@link BluetoothHapClient}
+     * service.
+     */
+    @Test
+    public void switchToNextPresetForGroup_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.switchToNextPresetForGroup(TEST_GROUP_ID);
+
+        verify(mService).switchToNextPresetForGroup(TEST_GROUP_ID);
+    }
+
+    /**
+     * Verify switchToPreviousPreset() call is correctly delegated to {@link BluetoothHapClient}
+     * service.
+     */
+    @Test
+    public void switchToPreviousPreset_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.switchToPreviousPreset(mBluetoothDevice);
+
+        verify(mService).switchToPreviousPreset(mBluetoothDevice);
+    }
+
+    /**
+     * Verify switchToPreviousPresetForGroup() call is correctly delegated to
+     * {@link BluetoothHapClient} service.
+     */
+    @Test
+    public void switchToPreviousPresetForGroup_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.switchToPreviousPresetForGroup(TEST_GROUP_ID);
+
+        verify(mService).switchToPreviousPresetForGroup(TEST_GROUP_ID);
+    }
+
+    /**
+     * Verify getPresetInfo() call is correctly delegated to {@link BluetoothHapClient} service and
+     * return correct object.
+     */
+    @Test
+    public void getPresetInfo_verifyIsCalledAndReturnCorrectObject() {
+        when(mService.getPresetInfo(mBluetoothDevice, TEST_PRESET_INDEX)).thenReturn(mPresetInfo);
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        final BluetoothHapPresetInfo info = mProfile.getPresetInfo(mBluetoothDevice,
+                TEST_PRESET_INDEX);
+
+        verify(mService).getPresetInfo(mBluetoothDevice, TEST_PRESET_INDEX);
+        assertThat(info).isEqualTo(mPresetInfo);
+    }
+
+    /**
+     * Verify getAllPresetInfo() call is correctly delegated to {@link BluetoothHapClient} service
+     * and return correct list.
+     */
+    @Test
+    public void getAllPresetInfo_verifyIsCalledAndReturnCorrectList() {
+        final List<BluetoothHapPresetInfo> testList = Arrays.asList(mPresetInfo, mPresetInfo);
+        when(mService.getAllPresetInfo(mBluetoothDevice)).thenReturn(testList);
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        final List<BluetoothHapPresetInfo> infoList = mProfile.getAllPresetInfo(mBluetoothDevice);
+
+        verify(mService).getAllPresetInfo(mBluetoothDevice);
+        assertThat(infoList.size()).isEqualTo(testList.size());
+    }
+
+    /**
+     * Verify setPresetName() call is correctly delegated to {@link BluetoothHapClient} service.
+     */
+    @Test
+    public void setPresetName_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.setPresetName(mBluetoothDevice, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
+
+        verify(mService).setPresetName(mBluetoothDevice, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
+    }
+
+    /**
+     * Verify setPresetNameForGroup() call is correctly delegated to {@link BluetoothHapClient}
+     * service.
+     */
+    @Test
+    public void setPresetNameForGroup_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        mProfile.setPresetNameForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
+
+        verify(mService).setPresetNameForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
new file mode 100644
index 0000000..c560627
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -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.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+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 org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class VolumeControlProfileTest {
+
+    private static final int TEST_VOLUME_OFFSET = 10;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private BluetoothVolumeControl mService;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private BluetoothProfile.ServiceListener mServiceListener;
+    private VolumeControlProfile mProfile;
+
+    @Before
+    public void setUp() {
+        mProfile = new VolumeControlProfile(mContext, mDeviceManager, mProfileManager);
+        final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        final ShadowBluetoothAdapter shadowBluetoothAdapter =
+                Shadow.extract(bluetoothManager.getAdapter());
+        mServiceListener = shadowBluetoothAdapter.getServiceListener();
+    }
+
+    @Test
+    public void onServiceConnected_isProfileReady() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+        assertThat(mProfile.isProfileReady()).isTrue();
+        verify(mProfileManager).callServiceConnectedListeners();
+    }
+
+    @Test
+    public void onServiceDisconnected_isProfileNotReady() {
+        mServiceListener.onServiceDisconnected(BluetoothProfile.VOLUME_CONTROL);
+
+        assertThat(mProfile.isProfileReady()).isFalse();
+        verify(mProfileManager).callServiceDisconnectedListeners();
+    }
+
+    @Test
+    public void getConnectionStatus_returnCorrectConnectionState() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionState(mBluetoothDevice))
+                .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyForbidden_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+    }
+
+    @Test
+    public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+                .isEqualTo(CONNECTION_POLICY_ALLOWED);
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void getConnectedDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        int[] connectedStates = new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectedList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectedStates))
+                .thenReturn(connectedList);
+
+        assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+    }
+
+    @Test
+    public void registerCallback_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+        final Executor executor = (command -> new Thread(command).start());
+        final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+        mProfile.registerCallback(executor, callback);
+
+        verify(mService).registerCallback(executor, callback);
+    }
+
+    @Test
+    public void unregisterCallback_verifyIsCalled() {
+        final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+        mProfile.unregisterCallback(callback);
+
+        verify(mService).unregisterCallback(callback);
+    }
+
+    @Test
+    public void setVolumeOffset_verifyIsCalled() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+        mProfile.setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
+
+        verify(mService).setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
+    }
+
+    @Test
+    public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
+
+        final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
+
+        verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
+        assertThat(available).isTrue();
+    }
+
+    @Test
+    public void isVolumeOffsetAvailable_verifyIsCalledAndReturnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(false);
+
+        final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
+
+        verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
+        assertThat(available).isFalse();
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
index 2b1e808..507dcbc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
@@ -55,8 +55,7 @@
     @Test
     public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
         CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
-        View contentFrameView =
-                layout.findViewById(com.android.settingslib.widget.R.id.content_frame);
+        View contentFrameView = layout.findViewById(R.id.content_frame);
 
         TextView textView = contentFrameView.findViewById(com.android.settingslib.robotests.R.id.text_hello_world);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
deleted file mode 100644
index 52d243a..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
+++ /dev/null
@@ -1,114 +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.settingslib.deviceinfo;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.robolectric.shadow.api.Shadow.extract;
-
-import android.os.UserManager;
-import android.telephony.TelephonyManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class,
-                SimStatusImeiInfoPreferenceControllerTest.ShadowTelephonyManager.class})
-public class SimStatusImeiInfoPreferenceControllerTest {
-
-    private AbstractSimStatusImeiInfoPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        mController = new AbstractSimStatusImeiInfoPreferenceController(
-                RuntimeEnvironment.application) {
-            @Override
-            public String getPreferenceKey() {
-                return null;
-            }
-        };
-    }
-
-    @Test
-    public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() {
-        ShadowUserManager userManager =
-                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
-        userManager.setIsAdminUser(true);
-        ShadowTelephonyManager telephonyManager =
-                extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class));
-        telephonyManager.setDataCapable(true);
-
-        assertThat(mController.isAvailable()).isTrue();
-    }
-
-    @Test
-    public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() {
-        ShadowUserManager userManager =
-                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
-        userManager.setIsAdminUser(true);
-        ShadowTelephonyManager telephonyManager =
-                extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class));
-        telephonyManager.setDataCapable(false);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
-    public void testIsAvailable_isNotAdmin_shouldReturnFalse() {
-        ShadowUserManager userManager =
-                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
-        userManager.setIsAdminUser(false);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Implements(UserManager.class)
-    public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
-
-        private boolean mAdminUser;
-
-        public void setIsAdminUser(boolean isAdminUser) {
-            mAdminUser = isAdminUser;
-        }
-
-        @Implementation
-        public boolean isAdminUser() {
-            return mAdminUser;
-        }
-    }
-
-    @Implements(TelephonyManager.class)
-    public static class ShadowTelephonyManager
-            extends org.robolectric.shadows.ShadowTelephonyManager {
-        private boolean mDataCapable = false;
-        private void setDataCapable(boolean capable) {
-            mDataCapable = capable;
-        }
-
-        @Implementation
-        public boolean isDataCapable() {
-            return mDataCapable;
-        }
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index ae54206..2e7905f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -59,155 +59,6 @@
     }
 
     @Test
-    public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_withPercentage() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                SEVENTEEN_MIN_MILLIS,
-                TEST_BATTERY_LEVEL_10,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                SEVENTEEN_MIN_MILLIS,
-                TEST_BATTERY_LEVEL_10,
-                false /* basedOnUsage */);
-
-        // We only add special mention for the long string
-        // ex: Will last about 1:15 PM based on your usage (10%)
-        assertThat(info).containsMatch(Pattern.compile(
-                NORMAL_CASE_EXPECTED_PREFIX
-                        + TIME_OF_DAY_REGEX
-                        + ENHANCED_SUFFIX
-                        + PERCENTAGE_REGEX));
-        // shortened string should not have extra text
-        // ex: Will last about 1:15 PM (10%)
-        assertThat(info2).containsMatch(Pattern.compile(
-                NORMAL_CASE_EXPECTED_PREFIX
-                        + TIME_OF_DAY_REGEX
-                        + PERCENTAGE_REGEX));
-    }
-
-    @Test
-    public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_noPercentage() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                SEVENTEEN_MIN_MILLIS,
-                null /* percentageString */,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                SEVENTEEN_MIN_MILLIS,
-                null /* percentageString */,
-                false /* basedOnUsage */);
-
-        // We only have % when it is provided
-        // ex: Will last about 1:15 PM based on your usage
-        assertThat(info).containsMatch(Pattern.compile(
-                NORMAL_CASE_EXPECTED_PREFIX
-                        + TIME_OF_DAY_REGEX
-                        + ENHANCED_SUFFIX
-                        + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage
-        // shortened string should not have extra text
-        // ex: Will last about 1:15 PM
-        assertThat(info2).containsMatch(Pattern.compile(
-                NORMAL_CASE_EXPECTED_PREFIX
-                        + TIME_OF_DAY_REGEX
-                        + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage
-    }
-
-    @Test
-    public void testGetBatteryRemainingStringFormatted_lessThanSevenMinutes_usesCorrectString() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                FIVE_MINUTES_MILLIS,
-                TEST_BATTERY_LEVEL_10 /* percentageString */,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                FIVE_MINUTES_MILLIS,
-                null /* percentageString */,
-                true /* basedOnUsage */);
-
-        // additional battery percentage in this string
-        assertThat(info.contains("may shut down soon (10%)")).isTrue();
-        // shortened string should not have percentage
-        assertThat(info2.contains("may shut down soon")).isTrue();
-    }
-
-    @Test
-    public void testGetBatteryRemainingStringFormatted_betweenSevenAndFifteenMinutes_usesCorrectString() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                TEN_MINUTES_MILLIS,
-                null /* percentageString */,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                TEN_MINUTES_MILLIS,
-                TEST_BATTERY_LEVEL_10 /* percentageString */,
-                true /* basedOnUsage */);
-
-        // shortened string should not have percentage
-        assertThat(info).isEqualTo("Less than 15 min left");
-        // Add percentage to string when provided
-        assertThat(info2).isEqualTo("Less than 15 min left (10%)");
-    }
-
-    @Test
-    public void testGetBatteryRemainingStringFormatted_betweenOneAndTwoDays_usesCorrectString() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                THIRTY_HOURS_MILLIS,
-                null /* percentageString */,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                THIRTY_HOURS_MILLIS,
-                TEST_BATTERY_LEVEL_10 /* percentageString */,
-                false /* basedOnUsage */);
-        String info3 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                THIRTY_HOURS_MILLIS + TEN_MINUTES_MILLIS,
-                null /* percentageString */,
-                false /* basedOnUsage */);
-
-        // We only add special mention for the long string
-        assertThat(info).isEqualTo("About 1 day, 6 hr left based on your usage");
-        // shortened string should not have extra text
-        assertThat(info2).isEqualTo("About 1 day, 6 hr left (10%)");
-        // present 2 time unit at most
-        assertThat(info3).isEqualTo("About 1 day, 6 hr left");
-    }
-
-    @Test
-    public void testGetBatteryRemainingStringFormatted_lessThanOneDay_usesCorrectString() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                TEN_HOURS_MILLIS,
-                null /* percentageString */,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                TEN_HOURS_MILLIS,
-                TEST_BATTERY_LEVEL_10 /* percentageString */,
-                false /* basedOnUsage */);
-        String info3 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                TEN_HOURS_MILLIS + TEN_MINUTES_MILLIS + TEN_SEC_MILLIS,
-                null /* percentageString */,
-                false /* basedOnUsage */);
-
-        // We only add special mention for the long string
-        assertThat(info).isEqualTo("About 10 hr left based on your usage");
-        // shortened string should not have extra text
-        assertThat(info2).isEqualTo("About 10 hr left (10%)");
-        // present 2 time unit at most
-        assertThat(info3).isEqualTo("About 10 hr, 10 min left");
-    }
-
-    @Test
-    public void testGetBatteryRemainingStringFormatted_moreThanTwoDays_usesCorrectString() {
-        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                THREE_DAYS_MILLIS,
-                null /* percentageString */,
-                true /* basedOnUsage */);
-        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
-                THREE_DAYS_MILLIS,
-                TEST_BATTERY_LEVEL_10 /* percentageString */,
-                true /* basedOnUsage */);
-
-        // shortened string should not have percentage
-        assertThat(info).isEqualTo("More than 2 days left");
-        // Add percentage to string when provided
-        assertThat(info2).isEqualTo("More than 2 days left (10%)");
-    }
-
-    @Test
     public void getBatteryTipStringFormatted_moreThanOneDay_usesCorrectString() {
         String info = PowerUtil.getBatteryTipStringFormatted(mContext,
                 THREE_DAYS_MILLIS);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
index 6195d75..71545b7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
@@ -36,10 +36,10 @@
 import android.graphics.drawable.ShapeDrawable;
 import android.os.Bundle;
 
-import com.android.settingslib.widget.adaptiveicon.R;
 import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.CategoryKey;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.widget.adaptiveicon.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -105,15 +105,15 @@
 
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
-                com.android.settingslib.widget.R.color.homepage_generic_icon_background));
+        assertThat(icon.mBackgroundColor).isEqualTo(
+                mContext.getColor(R.color.homepage_generic_icon_background));
     }
 
     @Test
     public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() {
         final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE));
         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT,
-                com.android.settingslib.widget.R.color.bt_outline_color);
+                R.color.bt_outline_color);
         doReturn(Icon.createWithResource(mContext, com.android.settingslib.R.drawable.ic_system_update))
                 .when(tile).getIcon(mContext);
 
@@ -121,8 +121,7 @@
                 new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK));
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
-                com.android.settingslib.widget.R.color.bt_outline_color));
+        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(R.color.bt_outline_color));
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index ccbe4f0..0ce83c6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -58,16 +58,14 @@
     @Test
     public void setLearnMoreText_shouldSetAsTextInLearnMore() {
         final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null));
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
         mFooterPreference.setLearnMoreText("Custom learn more");
         mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
 
         mFooterPreference.onBindViewHolder(holder);
 
-        assertThat(((TextView) holder.findViewById(
-                com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString())
-                .isEqualTo("Custom learn more");
+        TextView learnMoreView = (TextView) holder.findViewById(R.id.settingslib_learn_more);
+        assertThat(learnMoreView.getText().toString()).isEqualTo("Custom learn more");
     }
 
     @Test
@@ -95,8 +93,7 @@
     @Test
     public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(R.layout.preference_footer, null)));
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
         when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null);
 
         Throwable actualThrowable = null;
@@ -112,10 +109,8 @@
     @Test
     public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
-        when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more))
-                .thenReturn(null);
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
 
         Throwable actualThrowable = null;
         try {
@@ -130,8 +125,7 @@
     @Test
     public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
         when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
 
         Throwable actualThrowable = null;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
deleted file mode 100644
index 0b9ba8d..0000000
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.testutils.shadow;
-
-import static android.os.Build.VERSION_CODES.O;
-
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-
-@Implements(ActivityManager.class)
-public class ShadowActivityManager {
-    private static int sCurrentUserId = 0;
-    private static int sUserSwitchedTo = -1;
-
-    @Resetter
-    public static void reset() {
-        sCurrentUserId = 0;
-        sUserSwitchedTo = 0;
-    }
-
-    @Implementation
-    protected static int getCurrentUser() {
-        return sCurrentUserId;
-    }
-
-    @Implementation
-    protected boolean switchUser(int userId) {
-        sUserSwitchedTo = userId;
-        return true;
-    }
-
-    @Implementation(minSdk = O)
-    protected static IActivityManager getService() {
-        return ReflectionHelpers.createNullProxy(IActivityManager.class);
-    }
-
-    public boolean getSwitchUserCalled() {
-        return sUserSwitchedTo != -1;
-    }
-
-    public int getUserSwitchedTo() {
-        return sUserSwitchedTo;
-    }
-
-    public static void setCurrentUser(int userId) {
-        sCurrentUserId = userId;
-    }
-
-    public static ShadowActivityManager getShadow() {
-        return (ShadowActivityManager) Shadow.extract(
-                RuntimeEnvironment.application.getSystemService(ActivityManager.class));
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java
new file mode 100644
index 0000000..a9fd380
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils.shadow;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.hardware.display.ColorDisplayManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(ColorDisplayManager.class)
+public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager {
+
+    private boolean mIsReduceBrightColorsActivated;
+
+    @Implementation
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setReduceBrightColorsActivated(boolean activated) {
+        mIsReduceBrightColorsActivated = activated;
+        return true;
+    }
+
+    @Implementation
+    @SystemApi
+    public boolean isReduceBrightColorsActivated() {
+        return mIsReduceBrightColorsActivated;
+    }
+
+}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index f4ca260..a4a9290 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -17,9 +17,10 @@
     ],
 }
 
-android_app {
-    name: "SettingsProvider",
+android_library {
+    name: "SettingsProviderLib",
     defaults: ["platform_app_defaults"],
+    manifest: "AndroidManifestLib.xml",
     resource_dirs: ["res"],
     srcs: [
         "src/**/*.java",
@@ -32,38 +33,37 @@
     ],
     static_libs: [
         "device_config_service_flags_java",
-        "junit",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibDisplayUtils",
     ],
     platform_apis: true,
+}
+
+android_app {
+    name: "SettingsProvider",
+    defaults: ["platform_app_defaults"],
+    resource_dirs: [],
+    static_libs: ["SettingsProviderLib"],
+    platform_apis: true,
     certificate: "platform",
     privileged: true,
 }
 
 android_test {
     name: "SettingsProviderTest",
-    // Note we statically link several classes to do some unit tests.  It's not accessible otherwise
-    // because this test is not an instrumentation test. (because the target runs in the system process.)
     srcs: [
         "test/**/*.java",
-        "src/android/provider/settings/backup/*",
-        "src/android/provider/settings/validators/*",
-        "src/com/android/providers/settings/GenerationRegistry.java",
-        "src/com/android/providers/settings/SettingsBackupAgent.java",
-        "src/com/android/providers/settings/SettingsState.java",
-        "src/com/android/providers/settings/SettingsHelper.java",
-        "src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java",
     ],
     static_libs: [
+        // Note we statically link SettingsProviderLib to do some unit tests.  It's not accessible otherwise
+        // because this test is not an instrumentation test. (because the target runs in the system process.)
+        "SettingsProviderLib",
+
         "androidx.test.rules",
-        "device_config_service_flags_java",
         "flag-junit",
+        "junit",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
-        "SettingsLibDeviceStateRotationLock",
-        "SettingsLibDisplayUtils",
-        "platform-test-annotations",
         "truth",
     ],
     libs: [
@@ -71,12 +71,7 @@
         "android.test.mock",
         "unsupportedappusage",
     ],
-    resource_dirs: ["res"],
-    aaptflags: [
-        "--auto-add-overlay",
-        "--extra-packages",
-        "com.android.providers.settings",
-    ],
+    resource_dirs: [],
     platform_apis: true,
     certificate: "platform",
     test_suites: ["device-tests"],
diff --git a/packages/SettingsProvider/AndroidManifestLib.xml b/packages/SettingsProvider/AndroidManifestLib.xml
new file mode 100644
index 0000000..a20b539
--- /dev/null
+++ b/packages/SettingsProvider/AndroidManifestLib.xml
@@ -0,0 +1,2 @@
+<manifest package="com.android.providers.settings">
+</manifest>
diff --git a/packages/SettingsProvider/OWNERS b/packages/SettingsProvider/OWNERS
index 5ade971..86ae581 100644
--- a/packages/SettingsProvider/OWNERS
+++ b/packages/SettingsProvider/OWNERS
@@ -1,5 +1 @@
-hackbod@android.com
-hackbod@google.com
-narayan@google.com
-svetoslavganov@google.com
 include /PACKAGE_MANAGER_OWNERS
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 1d25ac7..e5dbe5f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -69,6 +69,7 @@
         Settings.Global.PRIVATE_DNS_SPECIFIER,
         Settings.Global.SOFT_AP_TIMEOUT_ENABLED,
         Settings.Global.ZEN_DURATION,
+        Settings.Global.REVERSE_CHARGING_AUTO_ON,
         Settings.Global.CHARGING_VIBRATION_ENABLED,
         Settings.Global.AWARE_ALLOWED,
         Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,                   // moved to secure
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 96f3290d..603e19f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -143,6 +143,7 @@
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
         Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
         Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
+        Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
         Settings.Secure.VOLUME_HUSH_GESTURE,
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
         Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
@@ -219,6 +220,7 @@
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
+        Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
         Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
         Settings.Secure.NOTIFICATION_BUBBLES,
         Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index f5d9475..59c3cd3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -84,6 +84,7 @@
         Settings.System.RING_VIBRATION_INTENSITY,
         Settings.System.HAPTIC_FEEDBACK_INTENSITY,
         Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+        Settings.System.KEYBOARD_VIBRATION_ENABLED,
         Settings.System.HAPTIC_FEEDBACK_ENABLED,
         Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
         Settings.System.DISPLAY_COLOR_MODE,
@@ -103,5 +104,8 @@
         Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
         Settings.System.PEAK_REFRESH_RATE,
         Settings.System.MIN_REFRESH_RATE,
+        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+        Settings.System.NOTIFICATION_COOLDOWN_ALL,
+        Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index ba06185..7e8fe7e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -171,6 +171,7 @@
         VALIDATORS.put(Global.WIFI_SCAN_THROTTLE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.APP_AUTO_RESTRICTION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.REVERSE_CHARGING_AUTO_ON, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f48600d..5457c35 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -212,6 +212,7 @@
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(
                 Secure.ENABLED_NOTIFICATION_LISTENERS,
@@ -308,6 +309,9 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE_NONE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 410269f..572303a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -31,6 +31,7 @@
 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;
 
@@ -137,6 +138,7 @@
         VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.KEYBOARD_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
@@ -239,5 +241,8 @@
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(System.PEAK_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
         VALIDATORS.put(System.MIN_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 02ec486..cd35f67 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -45,7 +45,8 @@
 
     private static final boolean DEBUG = false;
 
-    private final Object mLock;
+    // This lock is not the same lock used in SettingsProvider and SettingsState
+    private final Object mLock = new Object();
 
     // Key -> backingStore mapping
     @GuardedBy("mLock")
@@ -74,8 +75,7 @@
 
     private final int mMaxNumBackingStore;
 
-    GenerationRegistry(Object lock, int maxNumUsers) {
-        mLock = lock;
+    GenerationRegistry(int maxNumUsers) {
         // Add some buffer to maxNumUsers to accommodate corner cases when the actual number of
         // users in the system exceeds the limit
         maxNumUsers = maxNumUsers + 2;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index f06b31c..7db189b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1850,6 +1850,10 @@
                 SecureSettingsProto.Accessibility
                         .ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                SecureSettingsProto.Accessibility
+                        .ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
                 SecureSettingsProto.Accessibility
                         .ACCESSIBILITY_MAGNIFICATION_GESTURE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 46cd725..5acc1ca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -40,7 +40,6 @@
 import static com.android.providers.settings.SettingsState.isConfigSettingsKey;
 import static com.android.providers.settings.SettingsState.isGlobalSettingsKey;
 import static com.android.providers.settings.SettingsState.isSecureSettingsKey;
-import static com.android.providers.settings.SettingsState.isSsaidSettingsKey;
 import static com.android.providers.settings.SettingsState.isSystemSettingsKey;
 import static com.android.providers.settings.SettingsState.makeKey;
 
@@ -412,6 +411,9 @@
         SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext());
         synchronized (mLock) {
             mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked();
+            for (UserInfo user : mUserManager.getAliveUsers()) {
+                mSettingsRegistry.ensureSettingsForUserLocked(user.id);
+            }
             mSettingsRegistry.syncSsaidTableOnStartLocked();
         }
         mHandler.post(() -> {
@@ -427,65 +429,53 @@
     public Bundle call(String method, String name, Bundle args) {
         final int requestingUserId = getRequestingUserId(args);
         switch (method) {
-            case Settings.CALL_METHOD_GET_CONFIG: {
+            case Settings.CALL_METHOD_GET_CONFIG -> {
                 Setting setting = getConfigSetting(name);
                 return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_GET_GLOBAL: {
+            case Settings.CALL_METHOD_GET_GLOBAL -> {
                 Setting setting = getGlobalSetting(name);
                 return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_GET_SECURE: {
+            case Settings.CALL_METHOD_GET_SECURE -> {
                 Setting setting = getSecureSetting(name, requestingUserId);
                 return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_GET_SYSTEM: {
+            case Settings.CALL_METHOD_GET_SYSTEM -> {
                 Setting setting = getSystemSetting(name, requestingUserId);
                 return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_PUT_CONFIG: {
+            case Settings.CALL_METHOD_PUT_CONFIG -> {
                 String value = getSettingValue(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
                 insertConfigSetting(name, value, makeDefault);
-                break;
             }
-
-            case Settings.CALL_METHOD_PUT_GLOBAL: {
+            case Settings.CALL_METHOD_PUT_GLOBAL -> {
                 String value = getSettingValue(args);
                 String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
                 final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                 insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
                         overrideableByRestore);
-                break;
             }
-
-            case Settings.CALL_METHOD_PUT_SECURE: {
+            case Settings.CALL_METHOD_PUT_SECURE -> {
                 String value = getSettingValue(args);
                 String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
                 final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                 insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false,
                         overrideableByRestore);
-                break;
             }
-
-            case Settings.CALL_METHOD_PUT_SYSTEM: {
+            case Settings.CALL_METHOD_PUT_SYSTEM -> {
                 String value = getSettingValue(args);
                 boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                 insertSystemSetting(name, value, requestingUserId, overrideableByRestore);
-                break;
             }
-
-            case Settings.CALL_METHOD_SET_ALL_CONFIG: {
+            case Settings.CALL_METHOD_SET_ALL_CONFIG -> {
                 String prefix = getSettingPrefix(args);
                 Map<String, String> flags = getSettingFlags(args);
                 Bundle result = new Bundle();
@@ -493,120 +483,96 @@
                         setAllConfigSettings(prefix, flags));
                 return result;
             }
-
-            case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG: {
+            case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG -> {
                 final int mode = getSyncDisabledMode(args);
                 setSyncDisabledModeConfig(mode);
-                break;
             }
-
-            case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG: {
+            case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG -> {
                 Bundle result = new Bundle();
                 result.putInt(Settings.KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN,
                         getSyncDisabledModeConfig());
                 return result;
             }
-
-            case Settings.CALL_METHOD_RESET_CONFIG: {
+            case Settings.CALL_METHOD_RESET_CONFIG -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String prefix = getSettingPrefix(args);
                 resetConfigSetting(mode, prefix);
-                break;
             }
-
-            case Settings.CALL_METHOD_RESET_GLOBAL: {
+            case Settings.CALL_METHOD_RESET_GLOBAL -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String tag = getSettingTag(args);
                 resetGlobalSetting(requestingUserId, mode, tag);
-                break;
             }
-
-            case Settings.CALL_METHOD_RESET_SECURE: {
+            case Settings.CALL_METHOD_RESET_SECURE -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String tag = getSettingTag(args);
                 resetSecureSetting(requestingUserId, mode, tag);
-                break;
             }
-
-            case Settings.CALL_METHOD_RESET_SYSTEM: {
+            case Settings.CALL_METHOD_RESET_SYSTEM -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String tag = getSettingTag(args);
                 resetSystemSetting(requestingUserId, mode, tag);
-                break;
             }
-
-            case Settings.CALL_METHOD_DELETE_CONFIG: {
-                int rows  = deleteConfigSetting(name) ? 1 : 0;
+            case Settings.CALL_METHOD_DELETE_CONFIG -> {
+                int rows = deleteConfigSetting(name) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_DELETE_GLOBAL: {
+            case Settings.CALL_METHOD_DELETE_GLOBAL -> {
                 int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_DELETE_SECURE: {
+            case Settings.CALL_METHOD_DELETE_SECURE -> {
                 int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_DELETE_SYSTEM: {
+            case Settings.CALL_METHOD_DELETE_SYSTEM -> {
                 int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_LIST_CONFIG: {
+            case Settings.CALL_METHOD_LIST_CONFIG -> {
                 String prefix = getSettingPrefix(args);
                 Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
                         isTrackingGeneration(args));
                 reportDeviceConfigAccess(prefix);
                 return result;
             }
-
-            case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: {
+            case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> {
                 RemoteCallback callback = args.getParcelable(
                         Settings.CALL_METHOD_MONITOR_CALLBACK_KEY);
                 setMonitorCallback(callback);
-                break;
             }
-
-            case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG: {
+            case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG -> {
                 clearMonitorCallback();
-                break;
             }
-
-            case Settings.CALL_METHOD_LIST_GLOBAL: {
+            case Settings.CALL_METHOD_LIST_GLOBAL -> {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
                         buildSettingsList(getAllGlobalSettings(null)));
                 return result;
             }
-
-            case Settings.CALL_METHOD_LIST_SECURE: {
+            case Settings.CALL_METHOD_LIST_SECURE -> {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
                         buildSettingsList(getAllSecureSettings(requestingUserId, null)));
                 return result;
             }
-
-            case Settings.CALL_METHOD_LIST_SYSTEM: {
+            case Settings.CALL_METHOD_LIST_SYSTEM -> {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
                         buildSettingsList(getAllSystemSettings(requestingUserId, null)));
                 return result;
             }
-
-            default: {
+            default -> {
                 Slog.w(LOG_TAG, "call() with invalid method: " + method);
-            } break;
+            }
         }
 
         return null;
@@ -638,7 +604,7 @@
         }
 
         switch (args.table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 if (args.name != null) {
                     Setting setting = getGlobalSetting(args.name);
                     return packageSettingForQuery(setting, normalizedProjection);
@@ -646,8 +612,7 @@
                     return getAllGlobalSettings(projection);
                 }
             }
-
-            case TABLE_SECURE: {
+            case TABLE_SECURE -> {
                 final int userId = UserHandle.getCallingUserId();
                 if (args.name != null) {
                     Setting setting = getSecureSetting(args.name, userId);
@@ -656,8 +621,7 @@
                     return getAllSecureSettings(userId, projection);
                 }
             }
-
-            case TABLE_SYSTEM: {
+            case TABLE_SYSTEM -> {
                 final int userId = UserHandle.getCallingUserId();
                 if (args.name != null) {
                     Setting setting = getSystemSetting(args.name, userId);
@@ -666,8 +630,7 @@
                     return getAllSystemSettings(userId, projection);
                 }
             }
-
-            default: {
+            default -> {
                 throw new IllegalArgumentException("Invalid Uri path:" + uri);
             }
         }
@@ -708,30 +671,27 @@
         String value = values.getAsString(Settings.Secure.VALUE);
 
         switch (table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 if (insertGlobalSetting(name, value, null, false,
                         UserHandle.getCallingUserId(), false,
                         /* overrideableByRestore */ false)) {
-                    return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
+                    return Uri.withAppendedPath(Global.CONTENT_URI, name);
                 }
-            } break;
-
-            case TABLE_SECURE: {
+            }
+            case TABLE_SECURE -> {
                 if (insertSecureSetting(name, value, null, false,
                         UserHandle.getCallingUserId(), false,
                         /* overrideableByRestore */ false)) {
-                    return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+                    return Uri.withAppendedPath(Secure.CONTENT_URI, name);
                 }
-            } break;
-
-            case TABLE_SYSTEM: {
+            }
+            case TABLE_SYSTEM -> {
                 if (insertSystemSetting(name, value, UserHandle.getCallingUserId(),
                         /* overridableByRestore */ false)) {
                     return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
                 }
-            } break;
-
-            default: {
+            }
+            default -> {
                 throw new IllegalArgumentException("Bad Uri path:" + uri);
             }
         }
@@ -775,22 +735,19 @@
         }
 
         switch (args.table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 final int userId = UserHandle.getCallingUserId();
                 return deleteGlobalSetting(args.name, userId, false) ? 1 : 0;
             }
-
-            case TABLE_SECURE: {
+            case TABLE_SECURE -> {
                 final int userId = UserHandle.getCallingUserId();
                 return deleteSecureSetting(args.name, userId, false) ? 1 : 0;
             }
-
-            case TABLE_SYSTEM: {
+            case TABLE_SYSTEM -> {
                 final int userId = UserHandle.getCallingUserId();
                 return deleteSystemSetting(args.name, userId) ? 1 : 0;
             }
-
-            default: {
+            default -> {
                 throw new IllegalArgumentException("Bad Uri path:" + uri);
             }
         }
@@ -816,24 +773,21 @@
         String value = values.getAsString(Settings.Secure.VALUE);
 
         switch (args.table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 final int userId = UserHandle.getCallingUserId();
                 return updateGlobalSetting(args.name, value, null, false,
                         userId, false) ? 1 : 0;
             }
-
-            case TABLE_SECURE: {
+            case TABLE_SECURE -> {
                 final int userId = UserHandle.getCallingUserId();
                 return updateSecureSetting(args.name, value, null, false,
                         userId, false) ? 1 : 0;
             }
-
-            case TABLE_SYSTEM: {
+            case TABLE_SYSTEM -> {
                 final int userId = UserHandle.getCallingUserId();
                 return updateSystemSetting(args.name, value, userId) ? 1 : 0;
             }
-
-            default: {
+            default -> {
                 throw new IllegalArgumentException("Invalid Uri path:" + uri);
             }
         }
@@ -1034,27 +988,38 @@
 
     private void registerBroadcastReceivers() {
         IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_ADDED);
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
         userFilter.addAction(Intent.ACTION_USER_STOPPED);
 
         getContext().registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                if (intent.getAction() == null) {
+                    return;
+                }
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_SYSTEM);
+                        UserHandle.USER_NULL);
+                if (userId == UserHandle.USER_NULL) {
+                    return;
+                }
 
                 switch (intent.getAction()) {
-                    case Intent.ACTION_USER_REMOVED: {
+                    case Intent.ACTION_USER_ADDED -> {
+                        synchronized (mLock) {
+                            mSettingsRegistry.ensureSettingsForUserLocked(userId);
+                        }
+                    }
+                    case Intent.ACTION_USER_REMOVED -> {
                         synchronized (mLock) {
                             mSettingsRegistry.removeUserStateLocked(userId, true);
                         }
-                    } break;
-
-                    case Intent.ACTION_USER_STOPPED: {
+                    }
+                    case Intent.ACTION_USER_STOPPED -> {
                         synchronized (mLock) {
                             mSettingsRegistry.removeUserStateLocked(userId, false);
                         }
-                    } break;
+                    }
                 }
             }
         }, userFilter);
@@ -1350,26 +1315,24 @@
         // Perform the mutation.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name));
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, name, value, null, makeDefault, true,
                             callingPackage, false, null,
                             /* overrideableByRestore */ false);
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name));
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, name, false, null);
                 }
-
-                case MUTATION_OPERATION_RESET: {
+                case MUTATION_OPERATION_RESET -> {
                     enforceDeviceConfigWritePermission(getContext(),
                             getAllConfigFlags(prefix).keySet());
-                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
+                    return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, callingPackage, mode, null, prefix);
-                } return true;
+                }
             }
         }
 
@@ -1523,7 +1486,7 @@
         enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // If this is a setting that is currently restricted for this user, do not allow
         // unrestricting changes.
@@ -1536,28 +1499,25 @@
         // Perform the mutation.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
                             callingPackage, forceNotify,
                             CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_UPDATE: {
+                case MUTATION_OPERATION_UPDATE -> {
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
                             callingPackage, forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_RESET: {
-                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL,
+                case MUTATION_OPERATION_RESET -> {
+                    return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, callingPackage, mode, tag);
-                } return true;
+                }
             }
         }
 
@@ -1580,12 +1540,12 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId);
 
         // The relevant "calling package" userId will be the owning userId for some
         // profiles, and we can't do the lookup inside our [lock held] loop, so work out
         // up front who the effective "new SSAID" user ID for that settings name will be.
-        final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId,
+        final int ssaidUserId = resolveOwningUserIdForSecureSetting(callingUserId,
                 Settings.Secure.ANDROID_ID);
         final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId);
 
@@ -1600,7 +1560,7 @@
             for (int i = 0; i < nameCount; i++) {
                 String name = names.get(i);
                 // Determine the owning user as some profile settings are cloned from the parent.
-                final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId,
+                final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId,
                         name);
 
                 if (!isSecureSettingAccessible(name)) {
@@ -1638,13 +1598,13 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // Ensure the caller can access the setting.
         enforceSettingReadable(name, SETTINGS_TYPE_SECURE, UserHandle.getCallingUserId());
 
         // Determine the owning user as some profile settings are cloned from the parent.
-        final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+        final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name);
 
         if (!isSecureSettingAccessible(name)) {
             // This caller is not permitted to access this setting. Pretend the setting doesn't
@@ -1811,7 +1771,7 @@
         enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // If this is a setting that is currently restricted for this user, do not allow
         // unrestricting changes.
@@ -1820,7 +1780,7 @@
         }
 
         // Determine the owning user as some profile settings are cloned from the parent.
-        final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+        final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name);
 
         // Only the owning user can change the setting.
         if (owningUserId != callingUserId) {
@@ -1832,28 +1792,25 @@
         // Mutate the value.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
                             callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS,
                             overrideableByRestore);
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_UPDATE: {
+                case MUTATION_OPERATION_UPDATE -> {
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
                             callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_RESET: {
-                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
+                case MUTATION_OPERATION_RESET -> {
+                    return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
                             UserHandle.USER_SYSTEM, callingPackage, mode, tag);
-                } return true;
+                }
             }
         }
 
@@ -1866,7 +1823,7 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId);
 
         synchronized (mLock) {
             List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SYSTEM, callingUserId);
@@ -1903,7 +1860,7 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // Ensure the caller can access the setting.
         enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, UserHandle.getCallingUserId());
@@ -1978,7 +1935,7 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(runAsUserId);
 
         if (isSettingRestrictedForUser(name, callingUserId, value, Binder.getCallingUid())) {
             Slog.e(LOG_TAG, "UserId: " + callingUserId + " is disallowed to change system "
@@ -2012,37 +1969,30 @@
         // Mutate the value.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     validateSystemSettingValue(name, value);
                     success = mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, callingPackage,
                             false, null, overrideableByRestore);
-                    break;
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     success = mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, false, null);
-                    break;
                 }
-
-                case MUTATION_OPERATION_UPDATE: {
+                case MUTATION_OPERATION_UPDATE -> {
                     validateSystemSettingValue(name, value);
                     success = mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, callingPackage,
                             false, null);
-                    break;
                 }
-
-                case MUTATION_OPERATION_RESET: {
+                case MUTATION_OPERATION_RESET -> {
                     success = mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SYSTEM,
                             runAsUserId, callingPackage, mode, tag);
-                    break;
                 }
-
-                default:
+                default -> {
                     success = false;
                     Slog.e(LOG_TAG, "Unknown operation code: " + operation);
+                }
             }
         }
 
@@ -2113,8 +2063,8 @@
      * Returns {@code true} if the specified secure setting should be accessible to the caller.
      */
     private boolean isSecureSettingAccessible(String name) {
-        switch (name) {
-            case "bluetooth_address":
+        return switch (name) {
+            case "bluetooth_address" ->
                 // BluetoothManagerService for some reason stores the Android's Bluetooth MAC
                 // address in this secure setting. Secure settings can normally be read by any app,
                 // which thus enables them to bypass the recently introduced restrictions on access
@@ -2122,22 +2072,23 @@
                 // To mitigate this we make this setting available only to callers privileged to see
                 // this device's MAC addresses, same as through public API
                 // BluetoothAdapter.getAddress() (see BluetoothManagerService for details).
-                return getContext().checkCallingOrSelfPermission(
-                        Manifest.permission.LOCAL_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
-            default:
-                return true;
-        }
+                    getContext().checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS)
+                            == PackageManager.PERMISSION_GRANTED;
+            default -> true;
+        };
     }
 
-    private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) {
-        return resolveOwningUserIdLocked(userId, sSecureCloneToManagedSettings, setting);
+    private int resolveOwningUserIdForSecureSetting(int userId, String setting) {
+        // no need to lock because sSecureCloneToManagedSettings is never modified
+        return resolveOwningUserId(userId, sSecureCloneToManagedSettings, setting);
     }
 
+    @GuardedBy("mLock")
     private int resolveOwningUserIdForSystemSettingLocked(int userId, String setting) {
         final int parentId;
         // Resolves dependency if setting has a dependency and the calling user has a parent
         if (sSystemCloneFromParentOnDependency.containsKey(setting)
-                && (parentId = getGroupParentLocked(userId)) != userId) {
+                && (parentId = getGroupParent(userId)) != userId) {
             // The setting has a dependency and the profile has a parent
             String dependency = sSystemCloneFromParentOnDependency.get(setting);
             // Lookup the dependency setting as ourselves, some callers may not have access to it.
@@ -2151,11 +2102,11 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
-        return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting);
+        return resolveOwningUserId(userId, sSystemCloneToManagedSettings, setting);
     }
 
-    private int resolveOwningUserIdLocked(int userId, Set<String> keys, String name) {
-        final int parentId = getGroupParentLocked(userId);
+    private int resolveOwningUserId(int userId, Set<String> keys, String name) {
+        final int parentId = getGroupParent(userId);
         if (parentId != userId && keys.contains(name)) {
             return parentId;
         }
@@ -2174,9 +2125,8 @@
         }
 
         switch (operation) {
-            case MUTATION_OPERATION_INSERT:
-                // Insert updates.
-            case MUTATION_OPERATION_UPDATE: {
+            // Insert updates.
+            case MUTATION_OPERATION_INSERT, MUTATION_OPERATION_UPDATE -> {
                 if (Settings.System.PUBLIC_SETTINGS.contains(name)) {
                     return;
                 }
@@ -2192,9 +2142,8 @@
 
                 warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
                         packageInfo.applicationInfo.targetSdkVersion, name);
-            } break;
-
-            case MUTATION_OPERATION_DELETE: {
+            }
+            case MUTATION_OPERATION_DELETE -> {
                 if (Settings.System.PUBLIC_SETTINGS.contains(name)
                         || Settings.System.PRIVATE_SETTINGS.contains(name)) {
                     throw new IllegalArgumentException("You cannot delete system defined"
@@ -2212,34 +2161,26 @@
 
                 warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
                         packageInfo.applicationInfo.targetSdkVersion, name);
-            } break;
+            }
         }
     }
 
-    private Set<String> getInstantAppAccessibleSettings(int settingsType) {
-        switch (settingsType) {
-            case SETTINGS_TYPE_GLOBAL:
-                return Settings.Global.INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SECURE:
-                return Settings.Secure.INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SYSTEM:
-                return Settings.System.INSTANT_APP_SETTINGS;
-            default:
-                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
-        }
+    private static Set<String> getInstantAppAccessibleSettings(int settingsType) {
+        return switch (settingsType) {
+            case SETTINGS_TYPE_GLOBAL -> Global.INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SECURE -> Secure.INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SYSTEM -> Settings.System.INSTANT_APP_SETTINGS;
+            default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+        };
     }
 
-    private Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) {
-        switch (settingsType) {
-            case SETTINGS_TYPE_GLOBAL:
-                return OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SYSTEM:
-                return OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SECURE:
-                return OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS;
-            default:
-                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
-        }
+    private static Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) {
+        return switch (settingsType) {
+            case SETTINGS_TYPE_GLOBAL -> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SYSTEM -> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SECURE -> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS;
+            default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+        };
     }
 
     @GuardedBy("mLock")
@@ -2270,7 +2211,7 @@
         switch (settingName) {
             // missing READ_PRIVILEGED_PHONE_STATE permission protection
             // see alternative API {@link SubscriptionManager#getPreferredDataSubscriptionId()
-            case Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION:
+            case Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION -> {
                 // app-compat handling, not break apps targeting on previous SDKs.
                 if (CompatChanges.isChangeEnabled(
                         ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL)) {
@@ -2278,7 +2219,7 @@
                             Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                             "access global settings MULTI_SIM_DATA_CALL_SUBSCRIPTION");
                 }
-                break;
+            }
         }
         if (!ai.isInstantApp()) {
             return;
@@ -2306,23 +2247,22 @@
         final Set<String> readableFields;
         final ArrayMap<String, Integer> readableFieldsWithMaxTargetSdk;
         switch (settingsType) {
-            case SETTINGS_TYPE_GLOBAL:
+            case SETTINGS_TYPE_GLOBAL -> {
                 allFields = sAllGlobalSettings;
                 readableFields = sReadableGlobalSettings;
                 readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk;
-                break;
-            case SETTINGS_TYPE_SYSTEM:
+            }
+            case SETTINGS_TYPE_SYSTEM -> {
                 allFields = sAllSystemSettings;
                 readableFields = sReadableSystemSettings;
                 readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk;
-                break;
-            case SETTINGS_TYPE_SECURE:
+            }
+            case SETTINGS_TYPE_SECURE -> {
                 allFields = sAllSecureSettings;
                 readableFields = sReadableSecureSettings;
                 readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk;
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+            }
+            default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType);
         }
 
         if (allFields.contains(settingName)) {
@@ -2380,7 +2320,7 @@
         throw new IllegalStateException("Calling package doesn't exist");
     }
 
-    private int getGroupParentLocked(int userId) {
+    private int getGroupParent(int userId) {
         // Most frequent use case.
         if (userId == UserHandle.USER_SYSTEM) {
             return userId;
@@ -2480,7 +2420,7 @@
         }
     }
 
-    private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) {
+    private static int resolveCallingUserIdEnforcingPermissions(int requestingUserId) {
         if (requestingUserId == UserHandle.getCallingUserId()) {
             return requestingUserId;
         }
@@ -2654,28 +2594,28 @@
     private static int getResetModeEnforcingPermission(Bundle args) {
         final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
         switch (mode) {
-            case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+            case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> {
                 if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
                     throw new SecurityException("Only system, shell/root on a "
                             + "debuggable build can reset to untrusted defaults");
                 }
                 return mode;
             }
-            case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+            case Settings.RESET_MODE_UNTRUSTED_CHANGES -> {
                 if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
                     throw new SecurityException("Only system, shell/root on a "
                             + "debuggable build can reset untrusted changes");
                 }
                 return mode;
             }
-            case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+            case Settings.RESET_MODE_TRUSTED_DEFAULTS -> {
                 if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
                     throw new SecurityException("Only system, shell/root on a "
                             + "debuggable build can reset to trusted defaults");
                 }
                 return mode;
             }
-            case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
+            case Settings.RESET_MODE_PACKAGE_DEFAULTS -> {
                 return mode;
             }
         }
@@ -2736,21 +2676,18 @@
             String column = cursor.getColumnName(i);
 
             switch (column) {
-                case Settings.NameValueTable._ID: {
+                case Settings.NameValueTable._ID -> {
                     values[i] = setting.getId();
-                } break;
-
-                case Settings.NameValueTable.NAME: {
+                }
+                case Settings.NameValueTable.NAME -> {
                     values[i] = setting.getName();
-                } break;
-
-                case Settings.NameValueTable.VALUE: {
+                }
+                case Settings.NameValueTable.VALUE -> {
                     values[i] = setting.getValue();
-                } break;
-
-                case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: {
+                }
+                case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE -> {
                     values[i] = String.valueOf(setting.isValuePreservedInRestore());
-                } break;
+                }
             }
         }
 
@@ -2762,19 +2699,11 @@
     }
 
     private String resolveCallingPackage() {
-        switch (Binder.getCallingUid()) {
-            case Process.ROOT_UID: {
-                return "root";
-            }
-
-            case Process.SHELL_UID: {
-                return "com.android.shell";
-            }
-
-            default: {
-                return getCallingPackage();
-            }
-        }
+        return switch (Binder.getCallingUid()) {
+            case Process.ROOT_UID -> "root";
+            case Process.SHELL_UID -> "com.android.shell";
+            default -> getCallingPackage();
+        };
     }
 
     private static final class Arguments {
@@ -2796,17 +2725,17 @@
         public Arguments(Uri uri, String where, String[] whereArgs, boolean supportAll) {
             final int segmentSize = uri.getPathSegments().size();
             switch (segmentSize) {
-                case 1: {
+                case 1 -> {
                     if (where != null
                             && (WHERE_PATTERN_WITH_PARAM_NO_BRACKETS.matcher(where).matches()
-                                || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches())
+                            || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches())
                             && whereArgs.length == 1) {
                         name = whereArgs[0];
                         table = computeTableForSetting(uri, name);
                         return;
                     } else if (where != null
                             && (WHERE_PATTERN_NO_PARAM_NO_BRACKETS.matcher(where).matches()
-                                || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) {
+                            || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) {
                         final int startIndex = Math.max(where.indexOf("'"),
                                 where.indexOf("\"")) + 1;
                         final int endIndex = Math.max(where.lastIndexOf("'"),
@@ -2819,15 +2748,14 @@
                         table = computeTableForSetting(uri, null);
                         return;
                     }
-                } break;
-
-                case 2: {
+                }
+                case 2 -> {
                     if (where == null && whereArgs == null) {
                         name = uri.getPathSegments().get(1);
                         table = computeTableForSetting(uri, name);
                         return;
                     }
-                } break;
+                }
             }
 
             EventLogTags.writeUnsupportedSettingsQuery(
@@ -2956,10 +2884,11 @@
 
         public SettingsRegistry() {
             mHandler = new MyHandler(getContext().getMainLooper());
-            mGenerationRegistry = new GenerationRegistry(mLock, UserManager.getMaxSupportedUsers());
+            mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers());
             mBackupManager = new BackupManager(getContext());
         }
 
+        @GuardedBy("mLock")
         private void generateUserKeyLocked(int userId) {
             // Generate a random key for each user used for creating a new ssaid.
             final byte[] keyBytes = new byte[32];
@@ -2983,6 +2912,7 @@
             return ByteBuffer.allocate(4).putInt(data.length).array();
         }
 
+        @GuardedBy("mLock")
         public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) {
             // Read the user's key from the ssaid table.
             Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
@@ -3044,6 +2974,7 @@
             return getSettingLocked(SETTINGS_TYPE_SSAID, userId, uid);
         }
 
+        @GuardedBy("mLock")
         private void syncSsaidTableOnStartLocked() {
             // Verify that each user's packages and ssaid's are in sync.
             for (UserInfo user : mUserManager.getAliveUsers()) {
@@ -3078,15 +3009,17 @@
             }
         }
 
+        @GuardedBy("mLock")
         public List<String> getSettingsNamesLocked(int type, int userId) {
             final int key = makeKey(type, userId);
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState == null) {
                 return new ArrayList<>();
             }
             return settingsState.getSettingNamesLocked();
         }
 
+        @GuardedBy("mLock")
         public SparseBooleanArray getKnownUsersLocked() {
             SparseBooleanArray users = new SparseBooleanArray();
             for (int i = mSettingsStates.size()-1; i >= 0; i--) {
@@ -3095,17 +3028,19 @@
             return users;
         }
 
+        @GuardedBy("mLock")
         @Nullable
         public SettingsState getSettingsLocked(int type, int userId) {
             final int key = makeKey(type, userId);
-            return peekSettingsStateLocked(key);
+            return mSettingsStates.get(key);
         }
 
-        public boolean ensureSettingsForUserLocked(int userId) {
+        @GuardedBy("mLock")
+        public void ensureSettingsForUserLocked(int userId) {
             // First make sure this user actually exists.
             if (mUserManager.getUserInfo(userId) == null) {
                 Slog.wtf(LOG_TAG, "Requested user " + userId + " does not exist");
-                return false;
+                return;
             }
 
             // Migrate the setting for this user if needed.
@@ -3143,9 +3078,9 @@
             // Upgrade the settings to the latest version.
             UpgradeController upgrader = new UpgradeController(userId);
             upgrader.upgradeIfNeededLocked();
-            return true;
         }
 
+        @GuardedBy("mLock")
         private void ensureSettingsStateLocked(int key) {
             if (mSettingsStates.get(key) == null) {
                 final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
@@ -3155,6 +3090,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         public void removeUserStateLocked(int userId, boolean permanently) {
             // We always keep the global settings in memory.
 
@@ -3166,12 +3102,7 @@
                     mSettingsStates.remove(systemKey);
                     systemSettingsState.destroyLocked(null);
                 } else {
-                    systemSettingsState.destroyLocked(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSettingsStates.remove(systemKey);
-                        }
-                    });
+                    systemSettingsState.destroyLocked(() -> mSettingsStates.remove(systemKey));
                 }
             }
 
@@ -3183,12 +3114,7 @@
                     mSettingsStates.remove(secureKey);
                     secureSettingsState.destroyLocked(null);
                 } else {
-                    secureSettingsState.destroyLocked(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSettingsStates.remove(secureKey);
-                        }
-                    });
+                    secureSettingsState.destroyLocked(() -> mSettingsStates.remove(secureKey));
                 }
             }
 
@@ -3200,12 +3126,7 @@
                     mSettingsStates.remove(ssaidKey);
                     ssaidSettingsState.destroyLocked(null);
                 } else {
-                    ssaidSettingsState.destroyLocked(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSettingsStates.remove(ssaidKey);
-                        }
-                    });
+                    ssaidSettingsState.destroyLocked(() -> mSettingsStates.remove(ssaidKey));
                 }
             }
 
@@ -3213,6 +3134,7 @@
             mGenerationRegistry.onUserRemoved(userId);
         }
 
+        @GuardedBy("mLock")
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, String packageName, boolean forceNotify,
                 Set<String> criticalSettings, boolean overrideableByRestore) {
@@ -3220,6 +3142,7 @@
                     packageName, forceNotify, criticalSettings, overrideableByRestore);
         }
 
+        @GuardedBy("mLock")
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
                 boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
@@ -3232,7 +3155,7 @@
 
             boolean success = false;
             boolean wasUnsetNonPredefinedSetting = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) {
                     wasUnsetNonPredefinedSetting = true;
@@ -3243,7 +3166,7 @@
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
-                settingsState.persistSyncLocked();
+                settingsState.persistSettingsLocked();
             }
 
             if (forceNotify || success) {
@@ -3264,9 +3187,10 @@
          * Set Config Settings using consumed keyValues, returns true if the keyValues can be set,
          * false otherwise.
          */
+        @GuardedBy("mLock")
         public boolean setConfigSettingsLocked(int key, String prefix,
                 Map<String, String> keyValues, String packageName) {
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) {
                     return false;
@@ -3283,18 +3207,19 @@
             return true;
         }
 
+        @GuardedBy("mLock")
         public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
                 Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 success = settingsState.deleteSettingLocked(name);
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
-                settingsState.persistSyncLocked();
+                settingsState.persistSettingsLocked();
             }
 
             if (forceNotify || success) {
@@ -3306,20 +3231,21 @@
             return success;
         }
 
+        @GuardedBy("mLock")
         public boolean updateSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, String packageName, boolean forceNotify,
                 Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 success = settingsState.updateSettingLocked(name, value, tag,
                         makeDefault, packageName);
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
-                settingsState.persistSyncLocked();
+                settingsState.persistSettingsLocked();
             }
 
             if (forceNotify || success) {
@@ -3331,10 +3257,11 @@
             return success;
         }
 
+        @GuardedBy("mLock")
         public Setting getSettingLocked(int type, int userId, String name) {
             final int key = makeKey(type, userId);
 
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState == null) {
                 return null;
             }
@@ -3352,16 +3279,18 @@
             return Global.SECURE_FRP_MODE.equals(setting.getName());
         }
 
+        @GuardedBy("mLock")
         public boolean resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag) {
             return resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
                     null);
         }
 
+        @GuardedBy("mLock")
         public boolean resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag, @Nullable String prefix) {
             final int key = makeKey(type, userId);
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState == null) {
                 return false;
             }
@@ -3369,7 +3298,7 @@
             boolean success = false;
             banConfigurationIfNecessary(type, prefix, settingsState);
             switch (mode) {
-                case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
+                case Settings.RESET_MODE_PACKAGE_DEFAULTS -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
@@ -3385,13 +3314,12 @@
                             }
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
-                } break;
-
-                case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+                }
+                case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
@@ -3407,13 +3335,12 @@
                             }
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
-                } break;
-
-                case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+                }
+                case Settings.RESET_MODE_UNTRUSTED_CHANGES -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
@@ -3435,13 +3362,12 @@
                             }
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
-                } break;
-
-                case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+                }
+                case Settings.RESET_MODE_TRUSTED_DEFAULTS -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         Setting setting = settingsState.getSettingLocked(name);
                         boolean someSettingChanged = false;
@@ -3460,15 +3386,16 @@
                             logSettingChanged(userId, name, type, CHANGE_TYPE_DELETE);
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
-                } break;
+                }
             }
             return success;
         }
 
+        @GuardedBy("mLock")
         public void removeSettingsForPackageLocked(String packageName, int userId) {
             // Global and secure settings are signature protected. Apps signed
             // by the platform certificate are generally not uninstalled  and
@@ -3482,6 +3409,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         public void onUidRemovedLocked(int uid) {
             final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID,
                     UserHandle.getUserId(uid));
@@ -3490,19 +3418,7 @@
             }
         }
 
-        @Nullable
-        private SettingsState peekSettingsStateLocked(int key) {
-            SettingsState settingsState = mSettingsStates.get(key);
-            if (settingsState != null) {
-                return settingsState;
-            }
-
-            if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) {
-                return null;
-            }
-            return mSettingsStates.get(key);
-        }
-
+        @GuardedBy("mLock")
         private void migrateAllLegacySettingsIfNeededLocked() {
             final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
             File globalFile = getSettingsFile(key);
@@ -3538,6 +3454,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void migrateLegacySettingsForUserIfNeededLocked(int userId) {
             // Every user has secure settings and if no file we need to migrate.
             final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
@@ -3552,6 +3469,7 @@
             migrateLegacySettingsForUserLocked(dbHelper, database, userId);
         }
 
+        @GuardedBy("mLock")
         private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
                 SQLiteDatabase database, int userId) {
             // Move over the system settings.
@@ -3559,7 +3477,7 @@
             ensureSettingsStateLocked(systemKey);
             SettingsState systemSettings = mSettingsStates.get(systemKey);
             migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
-            systemSettings.persistSyncLocked();
+            systemSettings.persistSettingsLocked();
 
             // Move over the secure settings.
             // Do this after System settings, since this is the first thing we check when deciding
@@ -3569,7 +3487,7 @@
             SettingsState secureSettings = mSettingsStates.get(secureKey);
             migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
             ensureSecureSettingAndroidIdSetLocked(secureSettings);
-            secureSettings.persistSyncLocked();
+            secureSettings.persistSettingsLocked();
 
             // Move over the global settings if owner.
             // Do this last, since this is the first thing we check when deciding
@@ -3585,7 +3503,7 @@
                             mSettingsCreationBuildId, null, true,
                             SettingsState.SYSTEM_PACKAGE_NAME);
                 }
-                globalSettings.persistSyncLocked();
+                globalSettings.persistSettingsLocked();
             }
 
             // Drop the database as now all is moved and persisted.
@@ -3596,6 +3514,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void migrateLegacySettingsLocked(SettingsState settingsState,
                 SQLiteDatabase database, String table) {
             SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
@@ -3630,7 +3549,7 @@
             }
         }
 
-        @GuardedBy("secureSettings.mLock")
+        @GuardedBy("mLock")
         private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
             Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);
 
@@ -3706,6 +3625,7 @@
                     name, type, changeType);
         }
 
+        @GuardedBy("mLock")
         private void notifyForConfigSettingsChangeLocked(int key, String prefix,
                 List<String> changedSettings) {
 
@@ -3787,30 +3707,18 @@
             }
         }
 
-        private File getSettingsFile(int key) {
-            if (isConfigSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_CONFIG);
-            } else if (isGlobalSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_GLOBAL);
-            } else if (isSystemSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_SYSTEM);
-            } else if (isSecureSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_SECURE);
-            } else if (isSsaidSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_SSAID);
-            } else {
-                throw new IllegalArgumentException("Invalid settings key:" + key);
-            }
+        private static File getSettingsFile(int key) {
+            final int userId = getUserIdFromKey(key);
+            final int type = getTypeFromKey(key);
+            final File userSystemDirectory = Environment.getUserSystemDirectory(userId);
+            return switch (type) {
+                case SETTINGS_TYPE_CONFIG -> new File(userSystemDirectory, SETTINGS_FILE_CONFIG);
+                case SETTINGS_TYPE_GLOBAL -> new File(userSystemDirectory, SETTINGS_FILE_GLOBAL);
+                case SETTINGS_TYPE_SYSTEM -> new File(userSystemDirectory, SETTINGS_FILE_SYSTEM);
+                case SETTINGS_TYPE_SECURE -> new File(userSystemDirectory, SETTINGS_FILE_SECURE);
+                case SETTINGS_TYPE_SSAID -> new File(userSystemDirectory, SETTINGS_FILE_SSAID);
+                default -> throw new IllegalArgumentException("Invalid settings key:" + key);
+            };
         }
 
         private Uri getNotificationUriFor(int key, String name) {
@@ -3833,14 +3741,11 @@
 
         private int getMaxBytesPerPackageForType(int type) {
             switch (type) {
-                case SETTINGS_TYPE_CONFIG:
-                case SETTINGS_TYPE_GLOBAL:
-                case SETTINGS_TYPE_SECURE:
-                case SETTINGS_TYPE_SSAID: {
+                case SETTINGS_TYPE_CONFIG, SETTINGS_TYPE_GLOBAL, SETTINGS_TYPE_SECURE,
+                        SETTINGS_TYPE_SSAID -> {
                     return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED;
                 }
-
-                default: {
+                default -> {
                     return SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED;
                 }
             }
@@ -3857,7 +3762,7 @@
             @Override
             public void handleMessage(Message msg) {
                 switch (msg.what) {
-                    case MSG_NOTIFY_URI_CHANGED: {
+                    case MSG_NOTIFY_URI_CHANGED -> {
                         final int userId = msg.arg1;
                         Uri uri = (Uri) msg.obj;
                         try {
@@ -3868,12 +3773,11 @@
                         if (DEBUG) {
                             Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
                         }
-                    } break;
-
-                    case MSG_NOTIFY_DATA_CHANGED: {
+                    }
+                    case MSG_NOTIFY_DATA_CHANGED -> {
                         mBackupManager.dataChanged();
                         scheduleWriteFallbackFilesJob();
-                    } break;
+                    }
                 }
             }
         }
@@ -3887,6 +3791,7 @@
                 mUserId = userId;
             }
 
+            @GuardedBy("mLock")
             public void upgradeIfNeededLocked() {
                 // The version of all settings for a user is the same (all users have secure).
                 SettingsState secureSettings = getSettingsLocked(
@@ -3944,18 +3849,22 @@
                 systemSettings.setVersionLocked(newVersion);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getGlobalSettingsLocked() {
                 return getSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getSecureSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getSsaidSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getSystemSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId);
             }
@@ -4404,16 +4313,16 @@
                     if (userId == UserHandle.USER_SYSTEM) {
                         SettingsState globalSettings = getGlobalSettingsLocked();
                         ensureLegacyDefaultValueAndSystemSetUpdatedLocked(globalSettings, userId);
-                        globalSettings.persistSyncLocked();
+                        globalSettings.persistSettingsLocked();
                     }
 
                     SettingsState secureSettings = getSecureSettingsLocked(mUserId);
                     ensureLegacyDefaultValueAndSystemSetUpdatedLocked(secureSettings, userId);
-                    secureSettings.persistSyncLocked();
+                    secureSettings.persistSettingsLocked();
 
                     SettingsState systemSettings = getSystemSettingsLocked(mUserId);
                     ensureLegacyDefaultValueAndSystemSetUpdatedLocked(systemSettings, userId);
-                    systemSettings.persistSyncLocked();
+                    systemSettings.persistSettingsLocked();
 
                     currentVersion = 146;
                 }
@@ -5399,7 +5308,7 @@
                     // next version step.
                     // If this is a new profile, check if a secure setting exists for the
                     // owner of the profile and use that value for the work profile.
-                    int owningId = resolveOwningUserIdForSecureSettingLocked(userId,
+                    int owningId = resolveOwningUserIdForSecureSetting(userId,
                             NOTIFICATION_BUBBLES);
                     Setting previous = getGlobalSettingsLocked()
                             .getSettingLocked("notification_bubbles");
@@ -6068,18 +5977,22 @@
                 return currentVersion;
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
                 initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, int val) {
                 initGlobalSettingsDefaultValLocked(key, String.valueOf(val));
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, long val) {
                 initGlobalSettingsDefaultValLocked(key, String.valueOf(val));
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, String val) {
                 final SettingsState globalSettings = getGlobalSettingsLocked();
                 Setting currentSetting = globalSettings.getSettingLocked(key);
@@ -6198,6 +6111,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void ensureLegacyDefaultValueAndSystemSetUpdatedLocked(SettingsState settings,
                 int userId) {
             List<String> names = settings.getSettingNamesLocked();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index e9533e5..7cec99d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -72,6 +72,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * This class contains the state for one type of settings. It is responsible
@@ -589,9 +590,10 @@
     }
 
     // The settings provider must hold its lock when calling here.
-    public void persistSyncLocked() {
+    public void persistSettingsLocked() {
         mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
-        doWriteState();
+        // schedule a write operation right away
+        mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
     }
 
     // The settings provider must hold its lock when calling here.
@@ -1725,4 +1727,20 @@
             return mPackageToMemoryUsage.getOrDefault(packageName, 0);
         }
     }
+
+    /**
+     * Allow tests to wait for the handler to finish handling all the remaining messages
+     */
+    @VisibleForTesting
+    public void waitForHandler() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        synchronized (mLock) {
+            mHandler.post(latch::countDown);
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            // ignored
+        }
+    }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7bca944..9dacade 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -904,6 +904,7 @@
                         Settings.Secure.EXTRA_AUTOMATIC_POWER_SAVE_MODE,
                         Settings.Secure.GAME_DASHBOARD_ALWAYS_ON,
                         Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
+                        Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT,
                         Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING,
                         Settings.Secure.LOCATION_COARSE_ACCURACY_M,
                         Settings.Secure.LOCATION_SHOW_SYSTEM_OPS,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index 12865f4..8029785 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -36,7 +36,7 @@
 public class GenerationRegistryTest {
     @Test
     public void testGenerationsWithRegularSetting() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
+        final GenerationRegistry generationRegistry = new GenerationRegistry(2);
         final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
@@ -93,7 +93,7 @@
 
     @Test
     public void testGenerationsWithConfigSetting() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
+        final GenerationRegistry generationRegistry = new GenerationRegistry(1);
         final String prefix = "test_namespace/";
         final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
 
@@ -110,7 +110,7 @@
 
     @Test
     public void testMaxNumBackingStores() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
+        final GenerationRegistry generationRegistry = new GenerationRegistry(2);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
         for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) {
@@ -133,7 +133,7 @@
 
     @Test
     public void testMaxSizeBackingStore() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
+        final GenerationRegistry generationRegistry = new GenerationRegistry(1);
         final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
@@ -153,7 +153,7 @@
 
     @Test
     public void testUnsetSettings() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
+        final GenerationRegistry generationRegistry = new GenerationRegistry(1);
         final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
@@ -172,7 +172,7 @@
 
     @Test
     public void testGlobalSettings() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
+        final GenerationRegistry generationRegistry = new GenerationRegistry(2);
         final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0);
         final String testGlobalSetting = "test_global_setting";
         final Bundle b = new Bundle();
@@ -190,11 +190,11 @@
 
     @Test
     public void testNumberOfBackingStores() {
-        GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0);
+        GenerationRegistry generationRegistry = new GenerationRegistry(0);
         // Test that the capacity of the backing stores is always valid
         assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
                 GenerationRegistry.MIN_NUM_BACKING_STORE);
-        generationRegistry = new GenerationRegistry(new Object(), 100);
+        generationRegistry = new GenerationRegistry(100);
         // Test that the capacity of the backing stores is always valid
         assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
                 GenerationRegistry.MAX_NUM_BACKING_STORE);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
index 2b33057..2984cd3 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
@@ -17,10 +17,12 @@
 package com.android.providers.settings;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeTrue;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Process;
@@ -42,6 +44,8 @@
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
 
 @LargeTest
 public class InstallNonMarketAppsDeprecationTest extends BaseSettingsProviderTest {
@@ -54,35 +58,42 @@
     private boolean mSystemSetUserRestriction;
     private List<Integer> mUsersAddedByTest;
 
-    private String waitTillValueChanges(String errorMessage, String oldValue) {
+    private static String waitTillValueChanges(String errorMessage, @Nullable String oldValue,
+                                        Supplier<String> action) {
         boolean interrupted = false;
         final long startTime = SystemClock.uptimeMillis();
-        String newValue = getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS);
-        while (newValue.equals(oldValue) && SystemClock.uptimeMillis() <= (startTime
+        String newValue = action.get();
+        while (Objects.equals(newValue, oldValue) && SystemClock.uptimeMillis() <= (startTime
                 + USER_RESTRICTION_CHANGE_TIMEOUT)) {
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException exc) {
                 interrupted = true;
             }
-            newValue = getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS);
+            newValue = action.get();
         }
         if (interrupted) {
             Thread.currentThread().interrupt();
         }
-        assertFalse(errorMessage, oldValue.equals(newValue));
+        assertNotEquals(errorMessage, newValue, oldValue);
         return newValue;
     }
 
-    private String getSecureSettingForUserViaShell(int userId) throws IOException {
+    private String getSecureSettingForUserViaShell(int userId) {
         StringBuilder sb = new StringBuilder("settings get --user ");
         sb.append(userId + " secure ");
         sb.append(Settings.Secure.INSTALL_NON_MARKET_APPS);
         BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(
                 InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                         sb.toString()).getFileDescriptor())));
-        String line = reader.readLine();
-        return line.trim();
+        String line;
+        try {
+            line = reader.readLine();
+        } catch (IOException e) {
+            return null;
+        }
+        final String result = line.trim();
+        return (result.isEmpty() || result.equals("null")) ? null : result;
     }
 
     @Override
@@ -121,7 +132,10 @@
 
         UserInfo newUser = mUm.createUser("TEST_USER", 0);
         mUsersAddedByTest.add(newUser.id);
-        String value = getSecureSettingForUserViaShell(newUser.id);
+        // wait till SettingsProvider reacts to the USER_ADDED event
+        String value = waitTillValueChanges(
+                "install_non_market_apps should be set for the new user", null,
+                () -> getSecureSettingForUserViaShell(newUser.id));
         assertEquals("install_non_market_apps should be 1 for a new user", value, "1");
     }
 
@@ -140,13 +154,15 @@
         mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, !mHasUserRestriction);
         value = waitTillValueChanges(
                 "Changing user restriction did not change the value of install_non_market_apps",
-                value);
+                value,
+                () -> getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS));
         assertTrue("Invalid value", value.equals("1") || value.equals("0"));
 
         mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, mHasUserRestriction);
         value = waitTillValueChanges(
                 "Changing user restriction did not change the value of install_non_market_apps",
-                value);
+                value,
+                () -> getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS));
         assertTrue("Invalid value", value.equals("1") || value.equals("0"));
     }
 
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 df4d2a1..02a7bc1 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -76,6 +76,14 @@
         mSettingsFile.delete();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        if (mSettingsFile != null) {
+            mSettingsFile.delete();
+        }
+        super.tearDown();
+    }
+
     public void testIsBinary() {
         assertFalse(SettingsState.isBinary(" abc 日本語"));
 
@@ -149,11 +157,10 @@
      * Make sure settings can be written to a file and also can be read.
      */
     public void testReadWrite() {
-        final File file = new File(getContext().getCacheDir(), "setting.xml");
-        file.delete();
         final Object lock = new Object();
 
-        final SettingsState ssWriter = new SettingsState(getContext(), lock, file, 1,
+        assertFalse(mSettingsFile.exists());
+        final SettingsState ssWriter = new SettingsState(getContext(), lock, mSettingsFile, 1,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
 
@@ -162,11 +169,13 @@
         ssWriter.insertSettingLocked("k3", null, null, false, "p2");
         ssWriter.insertSettingLocked("k4", CRAZY_STRING, null, false, "p3");
         synchronized (lock) {
-            ssWriter.persistSyncLocked();
+            ssWriter.persistSettingsLocked();
         }
-
-        final SettingsState ssReader = new SettingsState(getContext(), lock, file, 1,
+        ssWriter.waitForHandler();
+        assertTrue(mSettingsFile.exists());
+        final SettingsState ssReader = new SettingsState(getContext(), lock, mSettingsFile, 1,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
         synchronized (lock) {
             assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
             assertEquals("abc", ssReader.getSettingLocked("k2").getValue());
@@ -179,10 +188,8 @@
      * In version 120, value "null" meant {code NULL}.
      */
     public void testUpgrade() throws Exception {
-        final File file = new File(getContext().getCacheDir(), "setting.xml");
-        file.delete();
         final Object lock = new Object();
-        final PrintStream os = new PrintStream(new FileOutputStream(file));
+        final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
         os.print(
                 "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
                         "<settings version=\"120\">" +
@@ -192,7 +199,7 @@
                         "</settings>");
         os.close();
 
-        final SettingsState ss = new SettingsState(getContext(), lock, file, 1,
+        final SettingsState ss = new SettingsState(getContext(), lock, mSettingsFile, 1,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         synchronized (lock) {
             SettingsState.Setting s;
@@ -213,7 +220,8 @@
     public void testInitializeSetting_preserveFlagNotSet() {
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -223,7 +231,8 @@
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
         settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -234,7 +243,8 @@
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
         settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
                 /* overrideableByRestore */ true);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -250,7 +260,8 @@
         // already been set to true.
         settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
                 /* overrideableByRestore */ true);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -481,8 +492,11 @@
             settingsState.insertSettingLocked(
                     FLAG_NAME_1_STAGED, VALUE1, null, false, TEST_PACKAGE);
             settingsState.insertSettingLocked(FLAG_NAME_2, VALUE2, null, false, TEST_PACKAGE);
-            settingsState.persistSyncLocked();
+            settingsState.persistSettingsLocked();
+        }
+        settingsState.waitForHandler();
 
+        synchronized (lock) {
             assertEquals(VALUE1, settingsState.getSettingLocked(FLAG_NAME_1_STAGED).getValue());
             assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());
         }
@@ -522,7 +536,10 @@
         synchronized (lock) {
             settingsState.insertSettingLocked(INVALID_STAGED_FLAG_1,
                     VALUE2, null, false, TEST_PACKAGE);
-            settingsState.persistSyncLocked();
+            settingsState.persistSettingsLocked();
+        }
+        settingsState.waitForHandler();
+        synchronized (lock) {
             assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
         }
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 11ae9c3..10d04d3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -860,6 +860,10 @@
     <!-- Permission required for CTS test - CtsWallpaperTestCases -->
     <uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" />
 
+    <!-- Permissions required for CTS test - CtsVoiceInteractionTestCases -->
+    <uses-permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" />
+    <uses-permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 235e672..2c89d6d 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -7,40 +7,22 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_library {
-    name: "SoundPickerLib",
-    srcs: [
-        "src/**/*.java",
-    ],
-    resource_dirs: [
-        "res",
-    ],
-    static_libs: [
-        "androidx.appcompat_appcompat",
-        "hilt_android",
-        "guava",
-        "androidx.recyclerview_recyclerview",
-        "androidx-constraintlayout_constraintlayout",
-        "androidx.viewpager2_viewpager2",
-        "com.google.android.material_material",
-    ],
-}
-
 android_app {
     name: "SoundPicker",
     defaults: ["platform_app_defaults"],
     manifest: "AndroidManifest.xml",
-    static_libs: ["SoundPickerLib"],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+
     platform_apis: true,
     certificate: "media",
     privileged: true,
-
-    optimize: {
-        enabled: true,
-        optimize: true,
-        shrink: true,
-        shrink_resources: true,
-        obfuscate: false,
-        proguard_compatibility: false,
-    },
 }
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
index 934b003..44295a5 100644
--- a/packages/SoundPicker/AndroidManifest.xml
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker"
-        android:sharedUserId="android.media">
+          package="com.android.soundpicker"
+          android:sharedUserId="android.media">
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -9,16 +9,12 @@
     <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
     <application
-            android:name=".RingtonePickerApplication"
-            android:allowBackup="false"
-            android:label="@string/app_label"
-            android:theme="@style/Theme.AppCompat"
-            android:supportsRtl="true">
+        android:allowBackup="false"
+        android:label="@string/app_label"
+        android:supportsRtl="true">
         <receiver android:name="RingtoneReceiver"
-                android:exported="true">
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
             </intent-filter>
@@ -27,17 +23,14 @@
         <service android:name="RingtoneOverlayService" />
 
         <activity android:name="RingtonePickerActivity"
-                android:theme="@style/Theme.AppCompat.Dialog"
-                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
-                android:excludeFromRecents="true"
-                android:exported="true">
+                  android:theme="@style/PickerDialogTheme"
+                  android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                  android:excludeFromRecents="true"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.RINGTONE_PICKER" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
             </intent-filter>
         </activity>
     </application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
index 024b97e..57b70d7 100644
--- a/packages/SoundPicker/res/layout/add_new_sound_item.xml
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -19,9 +19,7 @@
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:gravity="center_vertical"
-              android:background="?android:attr/selectableItemBackground"
-              android:focusable="true"
-              android:clickable="true">
+              android:background="?android:attr/selectableItemBackground">
 
     <ImageView
         android:layout_width="24dp"
@@ -31,19 +29,19 @@
         android:scaleType="centerCrop"
         android:layout_marginRight="24dp"
         android:layout_marginLeft="24dp"
-        android:src="@drawable/ic_add"/>
+        android:src="@drawable/ic_add" />
 
-    <TextView
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:maxLines="3"
-        android:gravity="center_vertical"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"/>
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/add_new_sound_text"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="?android:attr/listPreferredItemHeightSmall"
+              android:text="@null"
+              android:textColor="?android:attr/colorAccent"
+              android:textAppearance="?android:attr/textAppearanceMedium"
+              android:maxLines="3"
+              android:gravity="center_vertical"
+              android:paddingEnd="?android:attr/dialogPreferredPadding"
+              android:drawablePadding="20dp"
+              android:ellipsize="marquee" />
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
index 36ac93e..2e44b6f 100644
--- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 
-<com.android.soundpicker.CheckedListItem
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
-    android:clickable="true">
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="fill_parent"
+     android:layout_height="wrap_content"
+     android:gravity="center_vertical"
+     android:background="?android:attr/selectableItemBackground"
+    >
 
     <CheckedTextView
         android:id="@+id/checked_text_view"
@@ -37,7 +35,7 @@
         android:drawablePadding="20dp"
         android:ellipsize="marquee"
         android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3"/>
+        android:maxLines="3" />
 
     <ImageView
         android:id="@id/work_icon"
@@ -46,5 +44,5 @@
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
         android:scaleType="centerCrop"
-        android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
+        android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/values-af/strings.xml b/packages/SoundPicker/res/values-af/strings.xml
index 7396b76..fd857b1 100644
--- a/packages/SoundPicker/res/values-af/strings.xml
+++ b/packages/SoundPicker/res/values-af/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan nie gepasmaakte luitoon byvoeg nie"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan nie gepasmaakte luitoon uitvee nie"</string>
     <string name="app_label" msgid="3091611356093417332">"Klanke"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Die lys is leeg"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Klank"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasie"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index bd1c24b..85206c0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ብጁ የጥሪ ቅላጼን ማከል አልተቻለም"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ብጁ የጥሪ ቅላጼን መሰረዝ አልተቻለም"</string>
     <string name="app_label" msgid="3091611356093417332">"ድምፆች"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ዝርዝሩ ባዶ ነው"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ድምፅ"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ንዝረት"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index 805c7cf..f8844e9 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
     <string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
-    <string name="empty_list" msgid="2871978423955821191">"القائمة فارغة."</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"الصوت"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"الاهتزاز"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-as/strings.xml b/packages/SoundPicker/res/values-as/strings.xml
index 0a1cd1b..5d6bc5d 100644
--- a/packages/SoundPicker/res/values-as/strings.xml
+++ b/packages/SoundPicker/res/values-as/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন যোগ কৰিব পৰা নগ\'ল"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন মচিব পৰা নগ\'ল"</string>
     <string name="app_label" msgid="3091611356093417332">"ধ্বনিসমূহ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"সূচীখন খালী আছে"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ধ্বনি"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"কম্পন"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-az/strings.xml b/packages/SoundPicker/res/values-az/strings.xml
index a308329..e32c3eb 100644
--- a/packages/SoundPicker/res/values-az/strings.xml
+++ b/packages/SoundPicker/res/values-az/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Fərdi zəng səsi əlavə etmək mümkün deyil"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Fərdi zəng səsini silmək mümkün deyil"</string>
     <string name="app_label" msgid="3091611356093417332">"Səslər"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Siyahı boşdur"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Səs"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasiya"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
index 2a7a196..947c85c 100644
--- a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije uspelo"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije uspelo"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-be/strings.xml b/packages/SoundPicker/res/values-be/strings.xml
index 431a301..6f7fc68 100644
--- a/packages/SoundPicker/res/values-be/strings.xml
+++ b/packages/SoundPicker/res/values-be/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Немагчыма дадаць карыстальніцкі рынгтон"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Немагчыма выдаліць карыстальніцкі рынгтон"</string>
     <string name="app_label" msgid="3091611356093417332">"Гукі"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Спіс пусты"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Гук"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Вібрацыя"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-bg/strings.xml b/packages/SoundPicker/res/values-bg/strings.xml
index 7447af6..4277d28 100644
--- a/packages/SoundPicker/res/values-bg/strings.xml
+++ b/packages/SoundPicker/res/values-bg/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Персонализираната мелодия не може да се добави"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Персонализираната мелодия не може да се изтрие"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Списъкът е празен"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Вибриране"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-bn/strings.xml b/packages/SoundPicker/res/values-bn/strings.xml
index c31b36a..276594a 100644
--- a/packages/SoundPicker/res/values-bn/strings.xml
+++ b/packages/SoundPicker/res/values-bn/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"কাস্টম রিংটোন যোগ করা গেল না"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"কাস্টম রিংটোন মোছা গেল না"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"তালিকায় কিছু নেই"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"সাউন্ড"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ভাইব্রেশন"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-bs/strings.xml b/packages/SoundPicker/res/values-bs/strings.xml
index e65b90d..0c8d33f 100644
--- a/packages/SoundPicker/res/values-bs/strings.xml
+++ b/packages/SoundPicker/res/values-bs/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nije moguće dodati prilagođenu melodiju zvona"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nije moguće izbrisati prilagođenu melodiju zvona"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ca/strings.xml b/packages/SoundPicker/res/values-ca/strings.xml
index 33839bc..ed96f70 100644
--- a/packages/SoundPicker/res/values-ca/strings.xml
+++ b/packages/SoundPicker/res/values-ca/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"No es pot afegir el so de trucada personalitzat"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No es pot suprimir el so de trucada personalitzat"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
-    <string name="empty_list" msgid="2871978423955821191">"La llista és buida"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"So"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibració"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-cs/strings.xml b/packages/SoundPicker/res/values-cs/strings.xml
index 612a6b2..dc67c96 100644
--- a/packages/SoundPicker/res/values-cs/strings.xml
+++ b/packages/SoundPicker/res/values-cs/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Vlastní vyzvánění se nepodařilo přidat"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Vlastní vyzvánění se nepodařilo smazat"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Seznam je prázdný"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrace"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-da/strings.xml b/packages/SoundPicker/res/values-da/strings.xml
index 56c4d23..b4437dc 100644
--- a/packages/SoundPicker/res/values-da/strings.xml
+++ b/packages/SoundPicker/res/values-da/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Den tilpassede ringetone kunne ikke tilføjes"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Den tilpassede ringetone kunne ikke slettes"</string>
     <string name="app_label" msgid="3091611356093417332">"Lyde"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-de/strings.xml b/packages/SoundPicker/res/values-de/strings.xml
index b004e2a..8be3aaa 100644
--- a/packages/SoundPicker/res/values-de/strings.xml
+++ b/packages/SoundPicker/res/values-de/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Benutzerdefinierter Klingelton konnte nicht hinzugefügt werden"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Benutzerdefinierter Klingelton konnte nicht gelöscht werden"</string>
     <string name="app_label" msgid="3091611356093417332">"Töne"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Die Liste ist leer"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Ton"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-el/strings.xml b/packages/SoundPicker/res/values-el/strings.xml
index bbcb1f5..41e9b0c 100644
--- a/packages/SoundPicker/res/values-el/strings.xml
+++ b/packages/SoundPicker/res/values-el/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Δεν είναι δυνατή η προσθήκη προσαρμοσμένου ήχου κλήσης"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Δεν είναι δυνατή η διαγραφή προσαρμοσμένου ήχου κλήσης"</string>
     <string name="app_label" msgid="3091611356093417332">"Ήχοι"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Η λίστα είναι κενή"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Ήχος"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Δόνηση"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rAU/strings.xml b/packages/SoundPicker/res/values-en-rAU/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rAU/strings.xml
+++ b/packages/SoundPicker/res/values-en-rAU/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index d887082..b0708356 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rGB/strings.xml b/packages/SoundPicker/res/values-en-rGB/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rGB/strings.xml
+++ b/packages/SoundPicker/res/values-en-rGB/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rIN/strings.xml b/packages/SoundPicker/res/values-en-rIN/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rIN/strings.xml
+++ b/packages/SoundPicker/res/values-en-rIN/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rXC/strings.xml b/packages/SoundPicker/res/values-en-rXC/strings.xml
index d26c89b..8397e0b 100644
--- a/packages/SoundPicker/res/values-en-rXC/strings.xml
+++ b/packages/SoundPicker/res/values-en-rXC/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎Unable to add custom ringtone‎‏‎‎‏‎"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎Unable to delete custom ringtone‎‏‎‎‏‎"</string>
     <string name="app_label" msgid="3091611356093417332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎Sounds‎‏‎‎‏‎"</string>
-    <string name="empty_list" msgid="2871978423955821191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎The list is empty‎‏‎‎‏‎"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Sound‎‏‎‎‏‎"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎Vibration‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-es-rUS/strings.xml b/packages/SoundPicker/res/values-es-rUS/strings.xml
index 211bfed..5bf73b2 100644
--- a/packages/SoundPicker/res/values-es-rUS/strings.xml
+++ b/packages/SoundPicker/res/values-es-rUS/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se puede agregar el tono personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se puede borrar el tono personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
-    <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-es/strings.xml b/packages/SoundPicker/res/values-es/strings.xml
index 3fdad74..a77f656 100644
--- a/packages/SoundPicker/res/values-es/strings.xml
+++ b/packages/SoundPicker/res/values-es/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se ha podido añadir un tono de llamada personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se ha podido eliminar un tono de llamada personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
-    <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-et/strings.xml b/packages/SoundPicker/res/values-et/strings.xml
index 3d9ee94..fa680ac 100644
--- a/packages/SoundPicker/res/values-et/strings.xml
+++ b/packages/SoundPicker/res/values-et/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kohandatud helinat ei õnnestu lisada"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kohandatud helinat ei õnnestu kustutada"</string>
     <string name="app_label" msgid="3091611356093417332">"Helid"</string>
-    <string name="empty_list" msgid="2871978423955821191">"See loend on tühi"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Heli"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibreerimine"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-eu/strings.xml b/packages/SoundPicker/res/values-eu/strings.xml
index 1f929bf..e8e07fe 100644
--- a/packages/SoundPicker/res/values-eu/strings.xml
+++ b/packages/SoundPicker/res/values-eu/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ezin da gehitu tonu pertsonalizatua"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ezin da ezabatu tonu pertsonalizatua"</string>
     <string name="app_label" msgid="3091611356093417332">"Soinuak"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Zerrenda hutsik dago"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Soinua"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Dardara"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fa/strings.xml b/packages/SoundPicker/res/values-fa/strings.xml
index 3b75ac9..769d5d5 100644
--- a/packages/SoundPicker/res/values-fa/strings.xml
+++ b/packages/SoundPicker/res/values-fa/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"افزودن آهنگ زنگ سفارشی ممکن نیست"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حذف آهنگ زنگ سفارشی ممکن نیست"</string>
     <string name="app_label" msgid="3091611356093417332">"صداها"</string>
-    <string name="empty_list" msgid="2871978423955821191">"فهرست خالی است"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"صدا"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"لرزش"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fi/strings.xml b/packages/SoundPicker/res/values-fi/strings.xml
index 15bf658..fcda098 100644
--- a/packages/SoundPicker/res/values-fi/strings.xml
+++ b/packages/SoundPicker/res/values-fi/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Muokatun soittoäänen lisääminen epäonnistui."</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Muokatun soittoäänen poistaminen epäonnistui."</string>
     <string name="app_label" msgid="3091611356093417332">"Äänet"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Lista on tyhjä"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Ääni"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Värinä"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fr-rCA/strings.xml b/packages/SoundPicker/res/values-fr-rCA/strings.xml
index 1cc170f..4d4545f 100644
--- a/packages/SoundPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-fr-rCA/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
-    <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fr/strings.xml b/packages/SoundPicker/res/values-fr/strings.xml
index ade2c16..9452e70 100644
--- a/packages/SoundPicker/res/values-fr/strings.xml
+++ b/packages/SoundPicker/res/values-fr/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-gl/strings.xml b/packages/SoundPicker/res/values-gl/strings.xml
index 83f2b2e..59a9d06 100644
--- a/packages/SoundPicker/res/values-gl/strings.xml
+++ b/packages/SoundPicker/res/values-gl/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Non se pode engadir un ton de chamada personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Non se pode eliminar un ton de chamada personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
-    <string name="empty_list" msgid="2871978423955821191">"A lista está baleira"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-gu/strings.xml b/packages/SoundPicker/res/values-gu/strings.xml
index 8207512..209769f 100644
--- a/packages/SoundPicker/res/values-gu/strings.xml
+++ b/packages/SoundPicker/res/values-gu/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"કસ્ટમ રિંગટોન ઉમેરવામાં અસમર્થ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"કસ્ટમ રિંગટોન કાઢી નાખવામાં અસમર્થ"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"સૂચિ ખાલી છે"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"સાઉન્ડ"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"વાઇબ્રેશન"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hi/strings.xml b/packages/SoundPicker/res/values-hi/strings.xml
index 304201f..ab3b7f8 100644
--- a/packages/SoundPicker/res/values-hi/strings.xml
+++ b/packages/SoundPicker/res/values-hi/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"आपके मुताबिक रिंगटोन नहीं जोड़ी जा सकी"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आपके मुताबिक रिंगटोन नहीं हटाई जा सकी"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"यह सूची खाली है"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"साउंड"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"वाइब्रेशन"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hr/strings.xml b/packages/SoundPicker/res/values-hr/strings.xml
index 642c7d5..3adc500 100644
--- a/packages/SoundPicker/res/values-hr/strings.xml
+++ b/packages/SoundPicker/res/values-hr/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije moguće"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije moguće"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Popis je prazan"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hu/strings.xml b/packages/SoundPicker/res/values-hu/strings.xml
index 401da84..32d4ba9 100644
--- a/packages/SoundPicker/res/values-hu/strings.xml
+++ b/packages/SoundPicker/res/values-hu/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nem sikerült hozzáadni az egyéni csengőhangot"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nem sikerült törölni az egyéni csengőhangot"</string>
     <string name="app_label" msgid="3091611356093417332">"Hangok"</string>
-    <string name="empty_list" msgid="2871978423955821191">"A lista üres"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Hang"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Rezgés"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hy/strings.xml b/packages/SoundPicker/res/values-hy/strings.xml
index d57345c..da8934f 100644
--- a/packages/SoundPicker/res/values-hy/strings.xml
+++ b/packages/SoundPicker/res/values-hy/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Հնարավոր չէ հատուկ զանգերանգ ավելացնել"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Հնարավոր չէ ջնջել հատուկ զանգերանգը"</string>
     <string name="app_label" msgid="3091611356093417332">"Ձայներ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Ցանկը դատարկ է"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Ձայն"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Թրթռոց"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 518a09d..86dce64 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Daftar ini kosong"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Suara"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-is/strings.xml b/packages/SoundPicker/res/values-is/strings.xml
index 7f05c79..d0fce78 100644
--- a/packages/SoundPicker/res/values-is/strings.xml
+++ b/packages/SoundPicker/res/values-is/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Get ekki bætt sérsniðnum hringitóni við"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Get ekki eytt sérsniðnum hringitóni"</string>
     <string name="app_label" msgid="3091611356093417332">"Hljóð"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Listinn er auður"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Hljóð"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Titringur"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-it/strings.xml b/packages/SoundPicker/res/values-it/strings.xml
index e1d8e19..632cb41 100644
--- a/packages/SoundPicker/res/values-it/strings.xml
+++ b/packages/SoundPicker/res/values-it/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossibile aggiungere suoneria personalizzata"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossibile eliminare suoneria personalizzata"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"L\'elenco è vuoto"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Suoneria"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrazione"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-iw/strings.xml b/packages/SoundPicker/res/values-iw/strings.xml
index 238370c9..387b140 100644
--- a/packages/SoundPicker/res/values-iw/strings.xml
+++ b/packages/SoundPicker/res/values-iw/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"לא ניתן להוסיף רינגטון מותאם אישית"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"לא ניתן למחוק רינגטון מותאם אישית"</string>
     <string name="app_label" msgid="3091611356093417332">"צלילים"</string>
-    <string name="empty_list" msgid="2871978423955821191">"הרשימה ריקה"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"צליל"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"רטט"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ja/strings.xml b/packages/SoundPicker/res/values-ja/strings.xml
index 408e870..7c2aec6 100644
--- a/packages/SoundPicker/res/values-ja/strings.xml
+++ b/packages/SoundPicker/res/values-ja/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"カスタム着信音を追加できません"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"カスタム着信音を削除できません"</string>
     <string name="app_label" msgid="3091611356093417332">"サウンド"</string>
-    <string name="empty_list" msgid="2871978423955821191">"このリストは空です"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"音"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"バイブレーション"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ka/strings.xml b/packages/SoundPicker/res/values-ka/strings.xml
index 83dfc3c..1cfe240 100644
--- a/packages/SoundPicker/res/values-ka/strings.xml
+++ b/packages/SoundPicker/res/values-ka/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"მორგებული ზარის დამატება შეუძლებელია"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"მორგებული ზარის წაშლა შეუძლებელია"</string>
     <string name="app_label" msgid="3091611356093417332">"ხმები"</string>
-    <string name="empty_list" msgid="2871978423955821191">"სია ცარიელია"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ხმა"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ვიბრაცია"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-kk/strings.xml b/packages/SoundPicker/res/values-kk/strings.xml
index 1815a95..8c4c169 100644
--- a/packages/SoundPicker/res/values-kk/strings.xml
+++ b/packages/SoundPicker/res/values-kk/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Арнаулы рингтонды енгізу мүмкін емес"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Арнаулы рингтонды жою мүмкін емес"</string>
     <string name="app_label" msgid="3091611356093417332">"Дыбыстар"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Тізім бос."</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Дыбыс"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Діріл"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-km/strings.xml b/packages/SoundPicker/res/values-km/strings.xml
index 6ae048f..a334429 100644
--- a/packages/SoundPicker/res/values-km/strings.xml
+++ b/packages/SoundPicker/res/values-km/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"មិន​អាច​បន្ថែម​សំឡេង​រោទ៍​ផ្ទាល់ខ្លួន​បាន"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"មិន​អាច​លុប​សំឡេង​រោទ៍​ផ្ទាល់ខ្លួន​បាន​ទេ"</string>
     <string name="app_label" msgid="3091611356093417332">"សំឡេង"</string>
-    <string name="empty_list" msgid="2871978423955821191">"បញ្ជីគឺទទេ"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"សំឡេង"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ការញ័រ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-kn/strings.xml b/packages/SoundPicker/res/values-kn/strings.xml
index c528866..da90ccb 100644
--- a/packages/SoundPicker/res/values-kn/strings.xml
+++ b/packages/SoundPicker/res/values-kn/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ಕಸ್ಟಮ್ ರಿಂಗ್‌ಟೋನ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ಕಸ್ಟಮ್ ರಿಂಗ್‌ಟೋನ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
     <string name="app_label" msgid="3091611356093417332">"ಧ್ವನಿಗಳು"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ಪಟ್ಟಿ ಖಾಲಿಯಾಗಿದೆ"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ಧ್ವನಿ"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ವೈಬ್ರೇಷನ್"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ko/strings.xml b/packages/SoundPicker/res/values-ko/strings.xml
index bcab6b2..70554d6 100644
--- a/packages/SoundPicker/res/values-ko/strings.xml
+++ b/packages/SoundPicker/res/values-ko/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"맞춤 벨소리를 추가할 수 없습니다."</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"맞춤 벨소리를 삭제할 수 없습니다."</string>
     <string name="app_label" msgid="3091611356093417332">"소리"</string>
-    <string name="empty_list" msgid="2871978423955821191">"목록이 비어 있음"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"소리"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"진동"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ky/strings.xml b/packages/SoundPicker/res/values-ky/strings.xml
index babd8c0..3c95228 100644
--- a/packages/SoundPicker/res/values-ky/strings.xml
+++ b/packages/SoundPicker/res/values-ky/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Жеке рингтон кошулбай жатат"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Жеке рингтон жок кылынбай жатат"</string>
     <string name="app_label" msgid="3091611356093417332">"Үндөр"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Тизме бош"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Үн"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Дирилдөө"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-lo/strings.xml b/packages/SoundPicker/res/values-lo/strings.xml
index 5e496e8..8bcae0d 100644
--- a/packages/SoundPicker/res/values-lo/strings.xml
+++ b/packages/SoundPicker/res/values-lo/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"ສຽງ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ລາຍຊື່ຫວ່າງເປົ່າ"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ສຽງ"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ການສັ່ນເຕືອນ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-lt/strings.xml b/packages/SoundPicker/res/values-lt/strings.xml
index c68cc3b..c7ea369 100644
--- a/packages/SoundPicker/res/values-lt/strings.xml
+++ b/packages/SoundPicker/res/values-lt/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepavyksta pridėti tinkinto skambėjimo tono"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepavyksta ištrinti tinkinto skambėjimo tono"</string>
     <string name="app_label" msgid="3091611356093417332">"Garsai"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Sąrašas yra tuščias"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Garsas"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibravimas"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-lv/strings.xml b/packages/SoundPicker/res/values-lv/strings.xml
index fab7f8c..2a26289 100644
--- a/packages/SoundPicker/res/values-lv/strings.xml
+++ b/packages/SoundPicker/res/values-lv/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nevar pievienot pielāgotu zvana signālu"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nevar izdzēst pielāgotu zvana signālu"</string>
     <string name="app_label" msgid="3091611356093417332">"Skaņas"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Saraksts ir tukšs"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Skaņa"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrācija"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-mk/strings.xml b/packages/SoundPicker/res/values-mk/strings.xml
index 4f2322a..545d5ed 100644
--- a/packages/SoundPicker/res/values-mk/strings.xml
+++ b/packages/SoundPicker/res/values-mk/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не може да се додаде приспособена мелодија"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не може да се избрише приспособена мелодија"</string>
     <string name="app_label" msgid="3091611356093417332">"Звуци"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Списокот е празен"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Вибрации"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ml/strings.xml b/packages/SoundPicker/res/values-ml/strings.xml
index 16cf725..21da8e8 100644
--- a/packages/SoundPicker/res/values-ml/strings.xml
+++ b/packages/SoundPicker/res/values-ml/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ഇഷ്ടാനുസൃത റിംഗ്‌ടോൺ ചേർക്കാനാവില്ല"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ഇഷ്ടാനുസൃത റിംഗ്‌ടോൺ ഇല്ലാതാക്കാനാവില്ല"</string>
     <string name="app_label" msgid="3091611356093417332">"ശബ്‌ദങ്ങൾ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ലിസ്‌റ്റിൽ ഒന്നുമില്ല"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ശബ്‌ദം"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"വൈബ്രേഷൻ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-mn/strings.xml b/packages/SoundPicker/res/values-mn/strings.xml
index 6215a41..15f7d12 100644
--- a/packages/SoundPicker/res/values-mn/strings.xml
+++ b/packages/SoundPicker/res/values-mn/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Захиалгат хонхны ая нэмэх боломжгүй"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Захиалгат хонхны ая устгах боломжгүй"</string>
     <string name="app_label" msgid="3091611356093417332">"Дуу чимээ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Жагсаалт хоосон байна"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Дуу"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Чичиргээ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-mr/strings.xml b/packages/SoundPicker/res/values-mr/strings.xml
index fe2fe12..3ddb991 100644
--- a/packages/SoundPicker/res/values-mr/strings.xml
+++ b/packages/SoundPicker/res/values-mr/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"कस्टम रिंगटोन जोडण्यात अक्षम"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"कस्टम रिंगटोन हटविण्यात अक्षम"</string>
     <string name="app_label" msgid="3091611356093417332">"आवाज"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ही सूची रिकामी आहे"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"आवाज"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"व्हायब्रेशन"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ms/strings.xml b/packages/SoundPicker/res/values-ms/strings.xml
index 5788f18..9d87d72 100644
--- a/packages/SoundPicker/res/values-ms/strings.xml
+++ b/packages/SoundPicker/res/values-ms/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambah nada dering tersuai"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat memadamkan nada dering tersuai"</string>
     <string name="app_label" msgid="3091611356093417332">"Bunyi"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Senarai ini kosong"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Bunyi"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-my/strings.xml b/packages/SoundPicker/res/values-my/strings.xml
index 0a1a4cf..62163e9 100644
--- a/packages/SoundPicker/res/values-my/strings.xml
+++ b/packages/SoundPicker/res/values-my/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ထည့်သွင်း၍မရပါ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ဖျက်၍မရပါ"</string>
     <string name="app_label" msgid="3091611356093417332">"အသံများ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ဤစာရင်းတွင် မည်သည့်အရာမျှမရှိပါ"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"အသံ"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"တုန်ခါမှု"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-nb/strings.xml b/packages/SoundPicker/res/values-nb/strings.xml
index c315801..e4e259a 100644
--- a/packages/SoundPicker/res/values-nb/strings.xml
+++ b/packages/SoundPicker/res/values-nb/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan ikke legge til egendefinert ringelyd"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan ikke slette egendefinert ringelyd"</string>
     <string name="app_label" msgid="3091611356093417332">"Lyder"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrering"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ne/strings.xml b/packages/SoundPicker/res/values-ne/strings.xml
index b95b70c..0a2bceb 100644
--- a/packages/SoundPicker/res/values-ne/strings.xml
+++ b/packages/SoundPicker/res/values-ne/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"आफू अनुकूल रिङटोन थप्न सकिएन"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आफू अनुकूल रिङटोनलाई मेट्न सकिएन"</string>
     <string name="app_label" msgid="3091611356093417332">"ध्वनिहरू"</string>
-    <string name="empty_list" msgid="2871978423955821191">"यो सूची खाली छ"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ध्वनि"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"भाइब्रेसन"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-nl/strings.xml b/packages/SoundPicker/res/values-nl/strings.xml
index 192431b..5b6fb70 100644
--- a/packages/SoundPicker/res/values-nl/strings.xml
+++ b/packages/SoundPicker/res/values-nl/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Toevoegen van aangepaste ringtone is mislukt"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Verwijderen van aangepaste ringtone is mislukt"</string>
     <string name="app_label" msgid="3091611356093417332">"Geluiden"</string>
-    <string name="empty_list" msgid="2871978423955821191">"De lijst is leeg"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Geluid"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Trillen"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-or/strings.xml b/packages/SoundPicker/res/values-or/strings.xml
index 5d82039..45ce594 100644
--- a/packages/SoundPicker/res/values-or/strings.xml
+++ b/packages/SoundPicker/res/values-or/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"କଷ୍ଟମ୍‍ ରିଙ୍ଗଟୋନ୍‍ ଯୋଡ଼ିପାରିବ ନାହିଁ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"କଷ୍ଟମ୍‍ ରିଙ୍ଗଟୋନ୍‍ ଡିଲିଟ୍‍ କରିପାରିବ ନାହିଁ"</string>
     <string name="app_label" msgid="3091611356093417332">"ସାଉଣ୍ଡ"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ତାଲିକା ଖାଲି ଅଛି"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ସାଉଣ୍ଡ"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ଭାଇବ୍ରେସନ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pa/strings.xml b/packages/SoundPicker/res/values-pa/strings.xml
index 63ecb9d..1e62f64 100644
--- a/packages/SoundPicker/res/values-pa/strings.xml
+++ b/packages/SoundPicker/res/values-pa/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਸ਼ਾਮਲ ਕਰਨ ਦੇ ਅਯੋਗ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਮਿਟਾਉਣ ਦੇ ਅਯੋਗ"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ਇਹ ਸੂਚੀ ਖਾਲੀ ਹੈ"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ਅਵਾਜ਼"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"ਥਰਥਰਾਹਟ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pl/strings.xml b/packages/SoundPicker/res/values-pl/strings.xml
index 310f40f..1b3b5c4 100644
--- a/packages/SoundPicker/res/values-pl/strings.xml
+++ b/packages/SoundPicker/res/values-pl/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nie można dodać dzwonka niestandardowego"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nie można usunąć dzwonka niestandardowego"</string>
     <string name="app_label" msgid="3091611356093417332">"Dźwięki"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Lista jest pusta"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Dźwięk"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Wibracje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pt-rBR/strings.xml b/packages/SoundPicker/res/values-pt-rBR/strings.xml
index 9f58ca0..7b545e1 100644
--- a/packages/SoundPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rBR/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
-    <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pt-rPT/strings.xml b/packages/SoundPicker/res/values-pt-rPT/strings.xml
index fd4e69f..5d742f1 100644
--- a/packages/SoundPicker/res/values-pt-rPT/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rPT/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível eliminar o toque personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
-    <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pt/strings.xml b/packages/SoundPicker/res/values-pt/strings.xml
index 9f58ca0..7b545e1 100644
--- a/packages/SoundPicker/res/values-pt/strings.xml
+++ b/packages/SoundPicker/res/values-pt/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
-    <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ro/strings.xml b/packages/SoundPicker/res/values-ro/strings.xml
index 08d937c..58b5aeb 100644
--- a/packages/SoundPicker/res/values-ro/strings.xml
+++ b/packages/SoundPicker/res/values-ro/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nu se poate adăuga tonul de sonerie personalizat"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nu se poate șterge tonul de sonerie personalizat"</string>
     <string name="app_label" msgid="3091611356093417332">"Sunete"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Lista este goală"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sunet"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrație"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ru/strings.xml b/packages/SoundPicker/res/values-ru/strings.xml
index be5495a..0d48ac1 100644
--- a/packages/SoundPicker/res/values-ru/strings.xml
+++ b/packages/SoundPicker/res/values-ru/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не удалось добавить рингтон"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не удалось удалить рингтон"</string>
     <string name="app_label" msgid="3091611356093417332">"Звуки"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Список пуст"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Вибрация"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-si/strings.xml b/packages/SoundPicker/res/values-si/strings.xml
index 6ba86cb..1872b6b 100644
--- a/packages/SoundPicker/res/values-si/strings.xml
+++ b/packages/SoundPicker/res/values-si/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"අභිරුචි නාද රිද්මය එක් කළ නොහැකිය"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"අභිරුචි නාද රිද්මය මැකිය නොහැකිය"</string>
     <string name="app_label" msgid="3091611356093417332">"ශබ්ද"</string>
-    <string name="empty_list" msgid="2871978423955821191">"ලැයිස්තුව හිස් ය"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ශබ්දය"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"කම්පනය"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sk/strings.xml b/packages/SoundPicker/res/values-sk/strings.xml
index 8f54350..8ff6d12 100644
--- a/packages/SoundPicker/res/values-sk/strings.xml
+++ b/packages/SoundPicker/res/values-sk/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepodarilo sa pridať vlastný tón zvonenia"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepodarilo sa odstrániť vlastný tón zvonenia"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Zoznam je prázdny"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrácie"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sl/strings.xml b/packages/SoundPicker/res/values-sl/strings.xml
index 1a818b2..77a2a2c 100644
--- a/packages/SoundPicker/res/values-sl/strings.xml
+++ b/packages/SoundPicker/res/values-sl/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tona zvonjenja po meri ni mogoče dodati"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tona zvonjenja po meri ni mogoče izbrisati"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvoki"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Seznam je prazen"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Zvok"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sq/strings.xml b/packages/SoundPicker/res/values-sq/strings.xml
index 4c497bb..e35dd71 100644
--- a/packages/SoundPicker/res/values-sq/strings.xml
+++ b/packages/SoundPicker/res/values-sq/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nuk mund të shtojë ton zileje të personalizuar"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nuk mund të fshijë ton zileje të personalizuar"</string>
     <string name="app_label" msgid="3091611356093417332">"Tingujt"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Lista është bosh"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Me tingull"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Me dridhje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sr/strings.xml b/packages/SoundPicker/res/values-sr/strings.xml
index 2ec1f17..bc573f5 100644
--- a/packages/SoundPicker/res/values-sr/strings.xml
+++ b/packages/SoundPicker/res/values-sr/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Додавање прилагођене мелодије звона није успело"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Брисање прилагођене мелодије звона није успело"</string>
     <string name="app_label" msgid="3091611356093417332">"Звукови"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Листа је празна"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Вибрирање"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sv/strings.xml b/packages/SoundPicker/res/values-sv/strings.xml
index cb1cd3a..c1dd1c2 100644
--- a/packages/SoundPicker/res/values-sv/strings.xml
+++ b/packages/SoundPicker/res/values-sv/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Det gick inte att lägga till en egen ringsignal"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Det gick inte att radera den egna ringsignalen"</string>
     <string name="app_label" msgid="3091611356093417332">"Ljud"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Listan är tom"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Ljud"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sw/strings.xml b/packages/SoundPicker/res/values-sw/strings.xml
index 8fd6d58..b023450 100644
--- a/packages/SoundPicker/res/values-sw/strings.xml
+++ b/packages/SoundPicker/res/values-sw/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Imeshindwa kuongeza mlio maalum wa simu"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Imeshindwa kufuta mlio maalum wa simu"</string>
     <string name="app_label" msgid="3091611356093417332">"Sauti"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Orodha hii haina chochote"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Sauti"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Mtetemo"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ta/strings.xml b/packages/SoundPicker/res/values-ta/strings.xml
index 692e58a..38e45b7 100644
--- a/packages/SoundPicker/res/values-ta/strings.xml
+++ b/packages/SoundPicker/res/values-ta/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"பிரத்தியேக ரிங்டோனைச் சேர்க்க முடியவில்லை"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"பிரத்தியேக ரிங்டோனை நீக்க முடியவில்லை"</string>
     <string name="app_label" msgid="3091611356093417332">"ஒலிகள்"</string>
-    <string name="empty_list" msgid="2871978423955821191">"பட்டியல் காலியாக உள்ளது"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"ஒலி"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"அதிர்வு"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-te/strings.xml b/packages/SoundPicker/res/values-te/strings.xml
index ce13e53..2d03ac0 100644
--- a/packages/SoundPicker/res/values-te/strings.xml
+++ b/packages/SoundPicker/res/values-te/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"అనుకూల రింగ్‌టోన్‌ను జోడించలేకపోయింది"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"అనుకూల రింగ్‌టోన్‌ను తొలగించలేకపోయింది"</string>
     <string name="app_label" msgid="3091611356093417332">"ధ్వనులు"</string>
-    <string name="empty_list" msgid="2871978423955821191">"మీ లిస్ట్ ఖాళీగా ఉంది"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"సౌండ్"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"వైబ్రేషన్"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-th/strings.xml b/packages/SoundPicker/res/values-th/strings.xml
index d5dc9b7..cc2e43f 100644
--- a/packages/SoundPicker/res/values-th/strings.xml
+++ b/packages/SoundPicker/res/values-th/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ไม่สามารถเพิ่มเสียงเรียกเข้าที่กำหนดเอง"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ไม่สามารถลบเสียงเรียกเข้าที่กำหนดเอง"</string>
     <string name="app_label" msgid="3091611356093417332">"เสียง"</string>
-    <string name="empty_list" msgid="2871978423955821191">"รายการว่างเปล่า"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"เสียง"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"การสั่น"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-tl/strings.xml b/packages/SoundPicker/res/values-tl/strings.xml
index 4e3bf7e..c0c1712 100644
--- a/packages/SoundPicker/res/values-tl/strings.xml
+++ b/packages/SoundPicker/res/values-tl/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Hindi maidagdag ang custom na ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Hindi ma-delete ang custom na ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Mga Tunog"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Walang laman ang listahan"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Tunog"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Pag-vibrate"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-tr/strings.xml b/packages/SoundPicker/res/values-tr/strings.xml
index 51ac541..955c23f 100644
--- a/packages/SoundPicker/res/values-tr/strings.xml
+++ b/packages/SoundPicker/res/values-tr/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Özel zil sesi eklenemiyor"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Özel zil sesi silinemiyor"</string>
     <string name="app_label" msgid="3091611356093417332">"Sesler"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Liste boş"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Ses"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Titreşim"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-uk/strings.xml b/packages/SoundPicker/res/values-uk/strings.xml
index a905b95..42dbfb0 100644
--- a/packages/SoundPicker/res/values-uk/strings.xml
+++ b/packages/SoundPicker/res/values-uk/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не вдалося додати користувацький сигнал дзвінка"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не вдалося видалити користувацький сигнал дзвінка"</string>
     <string name="app_label" msgid="3091611356093417332">"Звуки"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Список пустий"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Вібрація"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ur/strings.xml b/packages/SoundPicker/res/values-ur/strings.xml
index 9cae118..58141d6 100644
--- a/packages/SoundPicker/res/values-ur/strings.xml
+++ b/packages/SoundPicker/res/values-ur/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"حسب ضرورت رنگ ٹون شامل کرنے سے قاصر ہے"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حسب ضرورت رنگ ٹون حذف کرنے سے قاصر ہے"</string>
     <string name="app_label" msgid="3091611356093417332">"آوازیں"</string>
-    <string name="empty_list" msgid="2871978423955821191">"فہرست خالی ہے"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"آواز"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"وائبریشن"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index e6231f2..c39db5f 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
     <string name="app_label" msgid="3091611356093417332">"Tovushlar"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Roʻyxat boʻsh"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Tovush"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Tebranish"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-vi/strings.xml b/packages/SoundPicker/res/values-vi/strings.xml
index 070ac16..bed0e96 100644
--- a/packages/SoundPicker/res/values-vi/strings.xml
+++ b/packages/SoundPicker/res/values-vi/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Không thể thêm nhạc chuông tùy chỉnh"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Không thể xóa nhạc chuông tùy chỉnh"</string>
     <string name="app_label" msgid="3091611356093417332">"Âm thanh"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Danh sách này đang trống"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Âm thanh"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Rung"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zh-rCN/strings.xml b/packages/SoundPicker/res/values-zh-rCN/strings.xml
index 0420fc9..864aaae 100644
--- a/packages/SoundPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rCN/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"无法添加自定义铃声"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"无法删除自定义铃声"</string>
     <string name="app_label" msgid="3091611356093417332">"声音"</string>
-    <string name="empty_list" msgid="2871978423955821191">"列表为空"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"声音"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"振动"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zh-rHK/strings.xml b/packages/SoundPicker/res/values-zh-rHK/strings.xml
index 60d9a52..4cde32d 100644
--- a/packages/SoundPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rHK/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法加入自訂鈴聲"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
     <string name="app_label" msgid="3091611356093417332">"音效"</string>
-    <string name="empty_list" msgid="2871978423955821191">"清單中沒有內容"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zh-rTW/strings.xml b/packages/SoundPicker/res/values-zh-rTW/strings.xml
index c173c0a..df8a66a 100644
--- a/packages/SoundPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rTW/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法新增自訂鈴聲"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
     <string name="app_label" msgid="3091611356093417332">"音效"</string>
-    <string name="empty_list" msgid="2871978423955821191">"清單中沒有任何項目"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zu/strings.xml b/packages/SoundPicker/res/values-zu/strings.xml
index 978e925..29a8ffe 100644
--- a/packages/SoundPicker/res/values-zu/strings.xml
+++ b/packages/SoundPicker/res/values-zu/strings.xml
@@ -26,7 +26,4 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ayikwazi ukwengeza ithoni yokukhala yangokwezifiso"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ayikwazi ukususa ithoni yokukhala yangokwezifiso"</string>
     <string name="app_label" msgid="3091611356093417332">"Imisindo"</string>
-    <string name="empty_list" msgid="2871978423955821191">"Uhlu alunalutho"</string>
-    <string name="sound_page_title" msgid="2143312098775103522">"Umsindo"</string>
-    <string name="vibration_page_title" msgid="6519501440349124677">"Ukudlidliza"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
index ab7b95a..04a2c2b 100644
--- a/packages/SoundPicker/res/values/strings.xml
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -40,8 +40,4 @@
 
     <!-- Text for the name of the app. [CHAR LIMIT=12] -->
     <string name="app_label">Sounds</string>
-
-    <string name="empty_list">The list is empty</string>
-    <string name="sound_page_title">Sound</string>
-    <string name="vibration_page_title">Vibration</string>
 </resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 90a14f9..ea46c0c 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -16,19 +16,43 @@
 
 package com.android.soundpicker;
 
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
 import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
 
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
 
-import dagger.hilt.android.AndroidEntryPoint;
+import java.io.IOException;
+import java.util.regex.Pattern;
 
 /**
  * The {@link RingtonePickerActivity} allows the user to choose one from all of the
@@ -36,183 +60,727 @@
  *
  * @see RingtoneManager#ACTION_RINGTONE_PICKER
  */
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+public final class RingtonePickerActivity extends AlertActivity implements
+        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+        AlertController.AlertParams.OnPrepareListViewListener {
+
+    private static final int POS_UNKNOWN = -1;
 
     private static final String TAG = "RingtonePickerActivity";
-    // TODO: Use the extra keys from RingtoneManager once they're added.
-    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
-    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
-    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
-    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
-    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
-    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
 
-    private RingtonePickerViewModel mRingtonePickerViewModel;
+    private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+
+    private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+    private static final int ADD_FILE_REQUEST_CODE = 300;
+
+    private RingtoneManager mRingtoneManager;
+    private int mType;
+
+    private Cursor mCursor;
+    private Handler mHandler;
+    private BadgedRingtoneAdapter mAdapter;
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentPos = POS_UNKNOWN;
+
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultRingtonePos = POS_UNKNOWN;
+
+    /** The position in the list of the ringtone to sample. */
+    private int mSampleRingtonePos = POS_UNKNOWN;
+
+    /** Whether this list has the 'Silent' item. */
+    private boolean mHasSilentItem;
+
+    /** The Uri to place a checkmark next to. */
+    private Uri mExistingUri;
+
+    /** The number of static items in the list. */
+    private int mStaticItemCount;
+
+    /** Whether this list has the 'Default' item. */
+    private boolean mHasDefaultItem;
+
+    /** The Uri to play when the 'Default' item is clicked. */
+    private Uri mUriForDefaultItem;
+
+    /** Id of the user to which the ringtone picker should list the ringtones */
+    private int mPickerUserId;
+
+    /** Context of the user specified by mPickerUserId */
+    private Context mTargetContext;
+
+    /**
+     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+     * will stop the previous ringtone. However, the RingtoneManager doesn't
+     * manage the default ringtone for us, so we should stop this one manually.
+     */
+    private Ringtone mDefaultRingtone;
+
+    /**
+     * The ringtone that's currently playing, unless the currently playing one is the default
+     * ringtone.
+     */
+    private Ringtone mCurrentRingtone;
+
+    /**
+     * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
+     */
+    private long mCheckedItemId = -1;
+
     private int mAttributesFlags;
 
+    private boolean mShowOkCancelButtons;
+
+    /**
+     * Keep the currently playing ringtone around when changing orientation, so that it
+     * can be stopped later, after the activity is recreated.
+     */
+    private static Ringtone sPlayingRingtone;
+
+    private DialogInterface.OnClickListener mRingtoneClickListener =
+            new DialogInterface.OnClickListener() {
+
+                /*
+                 * On item clicked
+                 */
+                public void onClick(DialogInterface dialog, int which) {
+                    if (which == mCursor.getCount() + mStaticItemCount) {
+                        // The "Add new ringtone" item was clicked. Start a file picker intent to select
+                        // only audio files (MIME type "audio/*")
+                        final Intent chooseFile = getMediaFilePickerIntent();
+                        startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
+                        return;
+                    }
+
+                    // Save the position of most recently clicked item
+                    setCheckedItem(which);
+
+                    // In the buttonless (watch-only) version, preemptively set our result since we won't
+                    // have another chance to do so before the activity closes.
+                    if (!mShowOkCancelButtons) {
+                        setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+                    }
+
+                    // Play clip
+                    playRingtone(which, 0);
+                }
+
+            };
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_ringtone_picker);
 
-        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+        mHandler = new Handler();
 
         Intent intent = getIntent();
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones
-         */
-        int pickerUserId = UserHandle.myUserId();
+        mPickerUserId = UserHandle.myUserId();
+        mTargetContext = this;
 
         // Get the types of ringtones to show
-        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
-                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+        mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+        initRingtoneManager();
 
+        /*
+         * Get whether to show the 'Default' item, and the URI to play when the
+         * default is clicked
+         */
+        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (mUriForDefaultItem == null) {
+            if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
+            } else if (mType == RingtoneManager.TYPE_ALARM) {
+                mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
+            } else if (mType == RingtoneManager.TYPE_RINGTONE) {
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            } else {
+                // or leave it null for silence.
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            }
+        }
+
+        // Get whether to show the 'Silent' item
+        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
         // AudioAttributes flags
         mAttributesFlags |= intent.getIntExtra(
                 RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
                 0 /*defaultValue == no flags*/);
 
-        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
-        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
-        if (title == null) {
-            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
-        }
-        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
-        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
-                ringtonePickerCategory);
-
-        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
-                ringtoneType);
-        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
-        RingtonePickerViewModel.Config pickerConfig =
-                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
-                        showOkCancelButtons, mAttributesFlags, pickerType);
-
-        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
-        if (savedInstanceState == null) {
-            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
-            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
-            if (prev != null) {
-                ft.remove(prev);
-            }
-            ft.addToBackStack(null);
-            dialogFragment.show(ft, TabbedDialogFragment.TAG);
-        }
+        mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
 
         // The volume keys will control the stream that we are choosing a ringtone for
-        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
-    }
+        setVolumeControlStream(mRingtoneManager.inferStreamType());
 
-    private RingtoneListHandler.Config getSoundListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
-        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a sound picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
-        boolean hasDefaultSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
-        // The Uri to play when the 'Default' sound item is clicked.
-        Uri uriForDefaultSoundItem =
-                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
-        if (uriForDefaultSoundItem == null) {
-            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
-        }
-
-        // Get whether this list has the 'Silent' sound item.
-        boolean hasSilentSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        // Get the sound URI whose list item should have a checkmark
-        Uri existingSoundUri = intent
+        // Get the URI whose list item should have a checkmark
+        mExistingUri = intent
                 .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
 
-        return new RingtoneListHandler.Config(hasDefaultSoundItem,
-                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
-    }
-
-    private RingtoneListHandler.Config getVibrationListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
-        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a vibration picker.
-            return null;
+        // Create the list of ringtones and hold on to it so we can update later.
+        mAdapter = new BadgedRingtoneAdapter(this, mCursor,
+                /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
+        if (savedInstanceState != null) {
+            setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
         }
 
-        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
-        boolean hasDefaultVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+        final AlertController.AlertParams p = mAlertParams;
+        p.mAdapter = mAdapter;
+        p.mOnClickListener = mRingtoneClickListener;
+        p.mLabelColumn = COLUMN_LABEL;
+        p.mIsSingleChoice = true;
+        p.mOnItemSelectedListener = this;
+        if (mShowOkCancelButtons) {
+            p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+            p.mPositiveButtonListener = this;
+            p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+            p.mPositiveButtonListener = this;
+        }
+        p.mOnPrepareListViewListener = this;
 
-        // The Uri to play when the 'Default' vibration item is clicked.
-        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (p.mTitle == null) {
+            if (mType == RingtoneManager.TYPE_ALARM) {
+                p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
+            } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                p.mTitle =
+                        getString(com.android.internal.R.string.ringtone_picker_title_notification);
+            } else {
+                p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+            }
+        }
 
-        // Get whether this list has the 'Silent' vibration item.
-        boolean hasSilentVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+        setupAlert();
 
-        // Get the vibration URI whose list item should have a checkmark
-        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(
-                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
-                existingVibrationUri);
+        ListView listView = mAlert.getListView();
+        if (listView != null) {
+            // List view needs to gain focus in order for RSB to work.
+            if (!listView.requestFocus()) {
+                Log.e(TAG, "Unable to gain focus! RSB may not work properly.");
+            }
+        }
+    }
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
     }
 
     @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
+            // Add the custom ringtone in a separate thread
+            final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
+                @Override
+                protected Uri doInBackground(Uri... params) {
+                    try {
+                        return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
+                    } catch (IOException | IllegalArgumentException e) {
+                        Log.e(TAG, "Unable to add new ringtone", e);
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Uri ringtoneUri) {
+                    if (ringtoneUri != null) {
+                        requeryForAdapter();
+                    } else {
+                        // Ringtone was not added, display error Toast
+                        Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            };
+            installTask.execute(data.getData());
+        }
+    }
+
+    // Disabled because context menus aren't Material Design :(
+    /*
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        int position = ((AdapterContextMenuInfo) menuInfo).position;
+
+        Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
+        if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
+            // It's a custom ringtone so we display the context menu
+            menu.setHeaderTitle(ringtone.getTitle(this));
+            menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case Menu.FIRST: {
+                int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
+                Uri deletedRingtoneUri = getRingtone(
+                        getRingtoneManagerPosition(deletedRingtonePos)).getUri();
+                if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
+                    requeryForAdapter();
+                } else {
+                    Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
+                            .show();
+                }
+                return true;
+            }
+            default: {
+                return false;
+            }
+        }
+    }
+    */
+
+    @Override
     public void onDestroy() {
-        mRingtonePickerViewModel.cancelPendingAsyncTasks();
+        if (mHandler != null) {
+            mHandler.removeCallbacksAndMessages(null);
+        }
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
         super.onDestroy();
     }
 
+    public void onPrepareListView(ListView listView) {
+        // Reset the static item count, as this method can be called multiple times
+        mStaticItemCount = 0;
+
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+            if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
+                setCheckedItem(mDefaultRingtonePos);
+            }
+        }
+
+        if (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+
+            // The 'Silent' item should use a null Uri
+            if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
+                setCheckedItem(mSilentPos);
+            }
+        }
+
+        if (getCheckedItem() == POS_UNKNOWN) {
+            setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
+        }
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+        // If external storage is available, add a button to install sounds from storage.
+        if (resolvesMediaFilePicker()
+                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            addNewSoundItem(listView);
+        }
+
+        // Enable context menu in ringtone items
+        registerForContextMenu(listView);
+    }
+
+    /**
+     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+     * selected item position to match the new position of the chosen sound.
+     *
+     * This should only need to happen after adding or removing a ringtone.
+     */
+    private void requeryForAdapter() {
+        // Refresh and set a new cursor, closing the old one.
+        initRingtoneManager();
+        mAdapter.changeCursor(mCursor);
+
+        // Update checked item location.
+        int checkedPosition = POS_UNKNOWN;
+        for (int i = 0; i < mAdapter.getCount(); i++) {
+            if (mAdapter.getItemId(i) == mCheckedItemId) {
+                checkedPosition = getListPosition(i);
+                break;
+            }
+        }
+        if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
+            checkedPosition = mSilentPos;
+        }
+        setCheckedItem(checkedPosition);
+        setupAlert();
+    }
+
+    /**
+     * Adds a static item to the top of the list. A static item is one that is not from the
+     * RingtoneManager.
+     *
+     * @param listView The ListView to add to.
+     * @param textResId The resource ID of the text for the item.
+     * @return The position of the inserted item.
+     */
+    private int addStaticItem(ListView listView, int textResId) {
+        TextView textView = (TextView) getLayoutInflater().inflate(
+                com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
+        textView.setText(textResId);
+        listView.addHeaderView(textView);
+        mStaticItemCount++;
+        return listView.getHeaderViewsCount() - 1;
+    }
+
+    private int addDefaultRingtoneItem(ListView listView) {
+        if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            return addStaticItem(listView, R.string.notification_sound_default);
+        } else if (mType == RingtoneManager.TYPE_ALARM) {
+            return addStaticItem(listView, R.string.alarm_sound_default);
+        }
+
+        return addStaticItem(listView, R.string.ringtone_default);
+    }
+
+    private int addSilentItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+    }
+
+    private void addNewSoundItem(ListView listView) {
+        View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
+                false /* attachToRoot */);
+        TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
+
+        if (mType == RingtoneManager.TYPE_ALARM) {
+            text.setText(R.string.add_alarm_text);
+        } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            text.setText(R.string.add_notification_text);
+        } else {
+            text.setText(R.string.add_ringtone_text);
+        }
+        listView.addFooterView(view);
+    }
+
+    private void initRingtoneManager() {
+        // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
+        // causes unexpected behavior.
+        mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
+        if (mType != -1) {
+            mRingtoneManager.setType(mType);
+        }
+        mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
+    }
+
+    private Ringtone getRingtone(int ringtoneManagerPosition) {
+        if (ringtoneManagerPosition < 0) {
+            return null;
+        }
+        return mRingtoneManager.getRingtone(ringtoneManagerPosition);
+    }
+
+    private int getCheckedItem() {
+        return mAlertParams.mCheckedItem;
+    }
+
+    private void setCheckedItem(int pos) {
+        mAlertParams.mCheckedItem = pos;
+        mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
+    }
+
+    /*
+     * On click of Ok/Cancel buttons
+     */
+    public void onClick(DialogInterface dialog, int which) {
+        boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
+
+        // Stop playing the previous ringtone
+        mRingtoneManager.stopPreviousRingtone();
+
+        if (positiveResult) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+
+        finish();
+    }
+
+    /*
+     * On item selected via keys
+     */
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        // footer view
+        if (position >= mCursor.getCount() + mStaticItemCount) {
+            return;
+        }
+
+        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void playRingtone(int position, int delayMs) {
+        mHandler.removeCallbacks(this);
+        mSampleRingtonePos = position;
+        mHandler.postDelayed(this, delayMs);
+    }
+
+    public void run() {
+        stopAnyPlayingRingtone();
+        if (mSampleRingtonePos == mSilentPos) {
+            return;
+        }
+
+        Ringtone ringtone;
+        if (mSampleRingtonePos == mDefaultRingtonePos) {
+            if (mDefaultRingtone == null) {
+                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+            }
+            /*
+             * Stream type of mDefaultRingtone is not set explicitly here.
+             * It should be set in accordance with mRingtoneManager of this Activity.
+             */
+            if (mDefaultRingtone != null) {
+                mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
+            }
+            ringtone = mDefaultRingtone;
+            mCurrentRingtone = null;
+        } else {
+            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+            mCurrentRingtone = ringtone;
+        }
+
+        if (ringtone != null) {
+            if (mAttributesFlags != 0) {
+                ringtone.setAudioAttributes(
+                        new AudioAttributes.Builder(ringtone.getAudioAttributes())
+                                .setFlags(mAttributesFlags)
+                                .build());
+            }
+            ringtone.play();
+        }
+    }
+
     @Override
     protected void onStop() {
         super.onStop();
-        mRingtonePickerViewModel.onStop(isChangingConfigurations());
+
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
+        } else {
+            saveAnyPlayingRingtone();
+        }
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        mRingtonePickerViewModel.onPause(isChangingConfigurations());
-    }
-
-    /**
-     * Maps the ringtone picker category to the appropriate PickerType.
-     * If the category is null or the feature is still not released, then it defaults to sound
-     * picker.
-     *
-     * @param category the ringtone picker category.
-     * @return the corresponding picker type.
-     */
-    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
-        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
-            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-
-        switch (category) {
-            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
-                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_SOUND":
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
-                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
-            default:
-                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
         }
     }
-}
+
+    private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+        setResult(RESULT_OK,
+                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+    }
+
+    private Uri getCurrentlySelectedRingtoneUri() {
+        if (getCheckedItem() == POS_UNKNOWN) {
+            // When the getCheckItem is POS_UNKNOWN, it is not the case we expected.
+            // We return null for this case.
+            return null;
+        } else if (getCheckedItem() == mDefaultRingtonePos) {
+            // Use the default Uri that they originally gave us.
+            return mUriForDefaultItem;
+        } else if (getCheckedItem() == mSilentPos) {
+            // Use a null Uri for the 'Silent' item.
+            return null;
+        } else {
+            return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+        }
+    }
+
+    private void saveAnyPlayingRingtone() {
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            sPlayingRingtone = mDefaultRingtone;
+        } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+            sPlayingRingtone = mCurrentRingtone;
+        }
+    }
+
+    private void stopAnyPlayingRingtone() {
+        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+            sPlayingRingtone.stop();
+        }
+        sPlayingRingtone = null;
+
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+        }
+
+        if (mRingtoneManager != null) {
+            mRingtoneManager.stopPreviousRingtone();
+        }
+    }
+
+    private int getRingtoneManagerPosition(int listPos) {
+        return listPos - mStaticItemCount;
+    }
+
+    private int getListPosition(int ringtoneManagerPos) {
+
+        // If the manager position is -1 (for not found), return that
+        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+
+        return ringtoneManagerPos + mStaticItemCount;
+    }
+
+    private Intent getMediaFilePickerIntent() {
+        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+        chooseFile.setType("audio/*");
+        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                new String[] { "audio/*", "application/ogg" });
+        return chooseFile;
+    }
+
+    private boolean resolvesMediaFilePicker() {
+        return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
+    }
+
+    private static class LocalizedCursor extends CursorWrapper {
+
+        final int mTitleIndex;
+        final Resources mResources;
+        String mNamePrefix;
+        final Pattern mSanitizePattern;
+
+        LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+            super(cursor);
+            mTitleIndex = mCursor.getColumnIndex(columnLabel);
+            mResources = resources;
+            mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+            if (mTitleIndex == -1) {
+                Log.e(TAG, "No index for column " + columnLabel);
+                mNamePrefix = null;
+            } else {
+                try {
+                    // Build the prefix for the name of the resource to look up
+                    // format is: "ResourcePackageName::ResourceTypeName/"
+                    // (the type name is expected to be "string" but let's not hardcode it).
+                    // Here we use an existing resource "notification_sound_default" which is
+                    // always expected to be found.
+                    mNamePrefix = String.format("%s:%s/%s",
+                            mResources.getResourcePackageName(R.string.notification_sound_default),
+                            mResources.getResourceTypeName(R.string.notification_sound_default),
+                            SOUND_NAME_RES_PREFIX);
+                } catch (NotFoundException e) {
+                    mNamePrefix = null;
+                }
+            }
+        }
+
+        /**
+         * Process resource name to generate a valid resource name.
+         * @param input
+         * @return a non-null String
+         */
+        private String sanitize(String input) {
+            if (input == null) {
+                return "";
+            }
+            return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            final String defaultName = mCursor.getString(columnIndex);
+            if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+                return defaultName;
+            }
+            TypedValue value = new TypedValue();
+            try {
+                // the name currently in the database is used to derive a name to match
+                // against resource names in this package
+                mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
+            } catch (NotFoundException e) {
+                // no localized string, use the default string
+                return defaultName;
+            }
+            if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+                Log.d(TAG, String.format("Replacing name %s with %s",
+                        defaultName, value.string.toString()));
+                return value.string.toString();
+            } else {
+                Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+                return defaultName;
+            }
+        }
+    }
+
+    private class BadgedRingtoneAdapter extends CursorAdapter {
+        private final boolean mIsManagedProfile;
+
+        public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
+            super(context, cursor);
+            mIsManagedProfile = isManagedProfile;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            if (position < 0) {
+                return position;
+            }
+            return super.getItemId(position);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            LayoutInflater inflater = LayoutInflater.from(context);
+            return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            // Set text as the title of the ringtone
+            ((TextView) view.findViewById(R.id.checked_text_view))
+                    .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
+
+            boolean isWorkRingtone = false;
+            if (mIsManagedProfile) {
+                /*
+                 * Display the work icon if the ringtone belongs to a work profile. We can tell that
+                 * a ringtone belongs to a work profile if the picker user is a managed profile, the
+                 * ringtone Uri is in external storage, and either the uri has no user id or has the
+                 * id of the picker user
+                 */
+                Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
+                int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
+                Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+                if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
+                        .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
+                    isWorkRingtone = true;
+                }
+            }
+
+            ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
+            if(isWorkRingtone) {
+                workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
+                        UserHandle.of(mPickerUserId), -1 /* density */));
+                workIcon.setVisibility(View.VISIBLE);
+            } else {
+                workIcon.setVisibility(View.GONE);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
new file mode 100644
index 0000000..f4d8bf2
--- /dev/null
+++ b/packages/SoundPicker2/Android.bp
@@ -0,0 +1,46 @@
+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_library {
+    name: "SoundPicker2Lib",
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "hilt_android",
+        "guava",
+        "androidx.recyclerview_recyclerview",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.viewpager2_viewpager2",
+        "com.google.android.material_material",
+    ],
+}
+
+android_app {
+    name: "SoundPicker2",
+    defaults: ["platform_app_defaults"],
+    manifest: "AndroidManifest.xml",
+    static_libs: ["SoundPicker2Lib"],
+    platform_apis: true,
+    certificate: "media",
+    privileged: true,
+
+    optimize: {
+        enabled: true,
+        optimize: true,
+        shrink: true,
+        shrink_resources: true,
+        obfuscate: false,
+        proguard_compatibility: false,
+    },
+}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
new file mode 100644
index 0000000..934b003
--- /dev/null
+++ b/packages/SoundPicker2/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.soundpicker"
+        android:sharedUserId="android.media">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+    <application
+            android:name=".RingtonePickerApplication"
+            android:allowBackup="false"
+            android:label="@string/app_label"
+            android:theme="@style/Theme.AppCompat"
+            android:supportsRtl="true">
+        <receiver android:name="RingtoneReceiver"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+            </intent-filter>
+        </receiver>
+
+        <service android:name="RingtoneOverlayService" />
+
+        <activity android:name="RingtonePickerActivity"
+                android:theme="@style/Theme.AppCompat.Dialog"
+                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                android:excludeFromRecents="true"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RINGTONE_PICKER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
new file mode 100644
index 0000000..5bf46e0
--- /dev/null
+++ b/packages/SoundPicker2/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team works on the SoundPicker
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
new file mode 100644
index 0000000..22b3fe9
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
new file mode 100644
index 0000000..c376867
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
@@ -0,0 +1,22 @@
+<!--
+    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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+        android:drawable="@drawable/ic_add"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
new file mode 100644
index 0000000..edfc0ab
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<!--
+     Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
+     Make the visibility to "gone" to prevent failures.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="@drawable/ic_add_padded"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
new file mode 100644
index 0000000..ee29a37
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    >
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3" />
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/activity_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
new file mode 100644
index 0000000..024b97e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
@@ -0,0 +1,49 @@
+<?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="fill_parent"
+              android:layout_height="wrap_content"
+              android:gravity="center_vertical"
+              android:background="?android:attr/selectableItemBackground"
+              android:focusable="true"
+              android:clickable="true">
+
+    <ImageView
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="24dp"
+        android:layout_marginLeft="24dp"
+        android:src="@drawable/ic_add"/>
+
+    <TextView
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:maxLines="3"
+        android:gravity="center_vertical"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml
rename to packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
new file mode 100644
index 0000000..36ac93e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+
+<com.android.soundpicker.CheckedListItem
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    android:focusable="true"
+    android:clickable="true">
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="20dp"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3"/>
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp"/>
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_ringtone.ogg
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
new file mode 100644
index 0000000..4e237a2
--- /dev/null
+++ b/packages/SoundPicker2/res/values/config.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue".  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+    ringtone will be automatically selected when the picker is closed. -->
+    <bool name="config_showOkCancelButtons">true</bool>
+</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
new file mode 100644
index 0000000..ab7b95a
--- /dev/null
+++ b/packages/SoundPicker2/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
+    <string name="ringtone_default">Default ringtone</string>
+
+    <!-- Choice in the notification sound picker.  If chosen, the default notification sound will be
+         used. -->
+    <string name="notification_sound_default">Default notification sound</string>
+
+    <!-- Choice in the alarm sound picker.  If chosen, the default alarm sound will be used. -->
+    <string name="alarm_sound_default">Default alarm sound</string>
+
+    <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
+    <string name="add_ringtone_text">Add ringtone</string>
+    <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
+    <string name="add_alarm_text">Add alarm</string>
+    <!-- Text for the RingtonePicker item that allows adding a new notification. -->
+    <string name="add_notification_text">Add notification</string>
+    <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
+    <string name="delete_ringtone_text">Delete</string>
+    <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
+    <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
+    <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
+    <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+
+    <!-- Text for the name of the app. [CHAR LIMIT=12] -->
+    <string name="app_label">Sounds</string>
+
+    <string name="empty_list">The list is empty</string>
+    <string name="sound_page_title">Sound</string>
+    <string name="vibration_page_title">Vibration</string>
+</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
new file mode 100644
index 0000000..d22d9c4
--- /dev/null
+++ b/packages/SoundPicker2/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
+    </style>
+
+</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
new file mode 100644
index 0000000..819ae98
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
@@ -0,0 +1,67 @@
+/*
+ * 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.soundpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.CheckedTextView;
+import android.widget.RelativeLayout;
+
+/**
+ * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
+ * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
+ * name if the ringtone belongs to a work profile.
+ */
+public class CheckedListItem extends RelativeLayout implements Checkable {
+
+    public CheckedListItem(Context context) {
+        super(context);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        getCheckedTextView().setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return getCheckedTextView().isChecked();
+    }
+
+    @Override
+    public void toggle() {
+        getCheckedTextView().toggle();
+    }
+
+    private CheckedTextView getCheckedTextView() {
+        return (CheckedTextView) findViewById(R.id.checked_text_view);
+    }
+
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java
rename to packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
new file mode 100644
index 0000000..b94ebeb
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -0,0 +1,113 @@
+/*
+ * 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.soundpicker;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Service to copy and set customization of default sounds
+ */
+public class RingtoneOverlayService extends Service {
+    private static final String TAG = "RingtoneOverlayService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
+        AsyncTask.execute(() -> {
+            updateRingtones();
+            stopSelf();
+        });
+
+        // Try again later if we are killed before we finish.
+        return Service.START_REDELIVER_INTENT;
+    }
+
+    @Override
+    public IBinder onBind(@Nullable final Intent intent) {
+        return null;
+    }
+
+    private void updateRingtones() {
+        copyResourceAndSetAsSound(R.raw.default_ringtone,
+                System.RINGTONE, Environment.DIRECTORY_RINGTONES);
+        copyResourceAndSetAsSound(R.raw.default_notification_sound,
+                System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
+        copyResourceAndSetAsSound(R.raw.default_alarm_alert,
+                System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
+    }
+
+    /* If the resource contains any data, copy a resource to the file system, scan it, and set the
+     * file URI as the default for a sound. */
+    private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
+            @NonNull final String subPath) {
+        final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
+        if (!destDir.exists() && !destDir.mkdirs()) {
+            Log.e(TAG, "can't create " + destDir.getAbsolutePath());
+            return;
+        }
+
+        final File dest = new File(destDir, "default_" + name + ".ogg");
+        try (
+                InputStream is = getResources().openRawResource(id);
+                FileOutputStream os = new FileOutputStream(dest);
+        ) {
+            if (is.available() > 0) {
+                FileUtils.copy(is, os);
+                final Uri uri = scanFile(dest);
+                if (uri != null) {
+                    set(name, uri);
+                }
+            } else {
+                // TODO Shall we remove any former copied resource in this case and unset
+                // the defaults if we use this event a second time to clear the data?
+                if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to open resource for " + name + ": " + e);
+        }
+    }
+
+    private Uri scanFile(@NonNull final File file) {
+        return MediaStore.scanFile(getContentResolver(), file);
+    }
+
+    private void set(@NonNull final String name, @NonNull final Uri uri) {
+        final Uri settingUri = System.getUriFor(name);
+        RingtoneManager.setActualDefaultRingtoneUri(this,
+                RingtoneManager.getDefaultType(settingUri), uri);
+        System.putInt(getContentResolver(), name + "_set", 1);
+    }
+}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
new file mode 100644
index 0000000..90a14f9
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.ViewModelProvider;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+@AndroidEntryPoint(AppCompatActivity.class)
+public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+
+    private static final String TAG = "RingtonePickerActivity";
+    // TODO: Use the extra keys from RingtoneManager once they're added.
+    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
+    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
+
+    private RingtonePickerViewModel mRingtonePickerViewModel;
+    private int mAttributesFlags;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_ringtone_picker);
+
+        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+
+        Intent intent = getIntent();
+        /**
+         * Id of the user to which the ringtone picker should list the ringtones
+         */
+        int pickerUserId = UserHandle.myUserId();
+
+        // Get the types of ringtones to show
+        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
+                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (title == null) {
+            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
+        }
+        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
+        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
+                ringtonePickerCategory);
+
+        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+                ringtoneType);
+        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
+
+        RingtonePickerViewModel.Config pickerConfig =
+                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+                        showOkCancelButtons, mAttributesFlags, pickerType);
+
+        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
+
+        if (savedInstanceState == null) {
+            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
+
+            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
+            if (prev != null) {
+                ft.remove(prev);
+            }
+            ft.addToBackStack(null);
+            dialogFragment.show(ft, TabbedDialogFragment.TAG);
+        }
+
+        // The volume keys will control the stream that we are choosing a ringtone for
+        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+    }
+
+    private RingtoneListHandler.Config getSoundListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a sound picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+        boolean hasDefaultSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+        // The Uri to play when the 'Default' sound item is clicked.
+        Uri uriForDefaultSoundItem =
+                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (uriForDefaultSoundItem == null) {
+            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+        }
+
+        // Get whether this list has the 'Silent' sound item.
+        boolean hasSilentSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        // Get the sound URI whose list item should have a checkmark
+        Uri existingSoundUri = intent
+                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(hasDefaultSoundItem,
+                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
+    }
+
+    private RingtoneListHandler.Config getVibrationListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a vibration picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+        boolean hasDefaultVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+
+        // The Uri to play when the 'Default' vibration item is clicked.
+        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+
+        // Get whether this list has the 'Silent' vibration item.
+        boolean hasSilentVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+        // Get the vibration URI whose list item should have a checkmark
+        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(
+                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+                existingVibrationUri);
+    }
+
+    @Override
+    public void onDestroy() {
+        mRingtonePickerViewModel.cancelPendingAsyncTasks();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mRingtonePickerViewModel.onStop(isChangingConfigurations());
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mRingtonePickerViewModel.onPause(isChangingConfigurations());
+    }
+
+    /**
+     * Maps the ringtone picker category to the appropriate PickerType.
+     * If the category is null or the feature is still not released, then it defaults to sound
+     * picker.
+     *
+     * @param category the ringtone picker category.
+     * @return the corresponding picker type.
+     */
+    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
+        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
+            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        }
+
+        switch (category) {
+            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
+                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_SOUND":
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
+                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
+            default:
+                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
diff --git a/services/core/java/com/android/server/BrickReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
similarity index 60%
rename from services/core/java/com/android/server/BrickReceiver.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
index cff3805..6a34936 100644
--- a/services/core/java/com/android/server/BrickReceiver.java
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2007 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.soundpicker;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.BroadcastReceiver;
-import android.os.SystemService;
-import android.util.Slog;
 
-public class BrickReceiver extends BroadcastReceiver {
+public class RingtoneReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
-        Slog.w("BrickReceiver", "!!! BRICKING DEVICE !!!");
-        SystemService.start("brick");
+        final String action = intent.getAction();
+        if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+            initResourceRingtones(context);
+        }
+    }
+
+    private void initResourceRingtones(Context context) {
+        context.startService(
+                new Intent(context, RingtoneOverlayService.class));
     }
 }
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
similarity index 94%
rename from packages/SoundPicker/tests/Android.bp
rename to packages/SoundPicker2/tests/Android.bp
index c38426f..d88d442 100644
--- a/packages/SoundPicker/tests/Android.bp
+++ b/packages/SoundPicker2/tests/Android.bp
@@ -17,7 +17,7 @@
 }
 
 android_test {
-    name: "SoundPickerTests",
+    name: "SoundPicker2Tests",
     certificate: "platform",
     libs: [
         "android.test.runner",
@@ -30,7 +30,7 @@
         "androidx.test.ext.truth",
         "mockito-target-minus-junit4",
         "guava-android-testlib",
-        "SoundPickerLib",
+        "SoundPicker2Lib",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/packages/SoundPicker/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
similarity index 100%
rename from packages/SoundPicker/tests/AndroidManifest.xml
rename to packages/SoundPicker2/tests/AndroidManifest.xml
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e40fcb2..17cc9f8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -60,7 +60,9 @@
             // except for SystemUI-core.
             // Copied from compose/features/Android.bp.
             static_libs: [
+                "CommunalLayoutLib",
                 "PlatformComposeCore",
+                "PlatformComposeSceneTransitionLayout",
 
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
@@ -212,6 +214,7 @@
 
     lint: {
         extra_check_modules: ["SystemUILintChecker"],
+        warning_checks: ["MissingApacheLicenseDetector"],
     },
 }
 
@@ -241,9 +244,6 @@
         "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
         "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
 
-        /* Log fakes */
-        "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt",
-
         /* QS fakes */
         "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
     ],
@@ -255,7 +255,6 @@
     srcs: [
         /* Keyguard converted tests */
         // data
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
@@ -403,10 +402,19 @@
         "tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt",
         "tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt",
         "tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt",
+
     ],
     path: "tests/src",
 }
 
+filegroup {
+    name: "SystemUI-tests-multivalent",
+    srcs: [
+        "multivalentTests/src/**/*.kt",
+    ],
+    path: "multivalentTests/src",
+}
+
 java_library {
     name: "SystemUI-tests-concurrency",
     srcs: [
@@ -435,6 +443,7 @@
         "SystemUI-statsd",
         "SettingsLib",
         "com_android_systemui_flags_lib",
+        "flag-junit-base",
         "androidx.viewpager2_viewpager2",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
@@ -476,6 +485,8 @@
         "motion_tool_lib",
         "androidx.core_core-animation-testing-nodeps",
         "androidx.compose.ui_ui",
+        "flag-junit",
+        "platform-test-annotations",
     ],
 }
 
@@ -494,6 +505,7 @@
         "src/**/*.java",
         "src/**/I*.aidl",
         ":ReleaseJavaFiles",
+        ":SystemUI-tests-multivalent",
         ":SystemUI-tests-utils",
     ],
     static_libs: [
@@ -572,6 +584,7 @@
         ":SystemUI-tests-utils",
         ":SystemUI-test-fakes",
         ":SystemUI-tests-robolectric-pilots",
+        ":SystemUI-tests-multivalent",
     ],
     static_libs: [
         "androidx.test.uiautomator_uiautomator",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9bfc4be..5881631 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -83,6 +83,7 @@
     <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
     <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+    <uses-permission android:name="android.permission.NETWORK_FACTORY" />
     <!-- Physical hardware -->
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -1062,5 +1063,9 @@
             <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
                 tools:node="remove" />
         </provider>
+
+        <!-- Allow SystemUI to listen for the capabilities defined in the linked xml -->
+        <property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES"
+                  android:value="@xml/self_certified_network_capabilities_both" />
     </application>
 </manifest>
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 8f329b3..34545cf 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -36,6 +36,7 @@
 hwwang@google.com
 hyunyoungs@google.com
 ikateryna@google.com
+iyz@google.com
 jaggies@google.com
 jamesoleary@google.com
 jbolinger@google.com
@@ -54,10 +55,12 @@
 kozynski@google.com
 kprevas@google.com
 lusilva@google.com
+liuyining@google.com
 lynhan@google.com
 madym@google.com
 mankoff@google.com
 mateuszc@google.com
+matiashe@google.com
 mgalhardo@google.com
 michaelmikhil@google.com
 michschn@google.com
@@ -94,6 +97,7 @@
 tsuji@google.com
 twickham@google.com
 vadimt@google.com
+valiiftime@google.com
 vanjan@google.com
 victortulias@google.com
 winsonc@google.com
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0623d4a..de73b77 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,22 +32,6 @@
       ]
     },
     {
-      "name": "SystemUIGoogleScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
-        }
-      ],
-      // The test doesn't run on AOSP Cuttlefish
-      "keywords": ["internal"]
-    },
-    {
       // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
       "name": "SystemUIGoogleBiometricsScreenshotTests",
       "options": [
@@ -144,9 +128,32 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
           "exclude-annotation": "android.platform.test.annotations.Postsubmit"
         }
       ]
     }
+  ],
+  // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged
+  "sysui-screenshot-test-staged": [
+    {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 0f55f35..eadcd7c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -1,15 +1,17 @@
 package: "com.android.systemui.accessibility.accessibilitymenu"
 
-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"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
     name: "a11y_menu_hide_before_taking_action"
     namespace: "accessibility"
     description: "Hides the AccessibilityMenuService UI before taking action instead of after."
     bug: "292020123"
-}
\ No newline at end of file
+}
+
+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-eu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
index f6dcdd3..b314c8e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
@@ -8,7 +8,7 @@
     <string name="a11y_settings_label" msgid="3977714687248445050">"Erabilerraztasun-&amp;#173;ezarpenak"</string>
     <string name="power_label" msgid="7699720321491287839">"Bateria"</string>
     <string name="power_utterance" msgid="7444296686402104807">"Bateria kontrolatzeko aukerak"</string>
-    <string name="recent_apps_label" msgid="6583276995616385847">"Azken aplikazioak"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Azkenaldiko aplikazioak"</string>
     <string name="lockscreen_label" msgid="648347953557887087">"Pantaila blokeatua"</string>
     <string name="quick_settings_label" msgid="2999117381487601865">"Ezarpen bizkorrak"</string>
     <string name="notifications_label" msgid="6829741046963013567">"Jakinarazpenak"</string>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
index e06787f..66e37a1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
@@ -10,7 +10,7 @@
     <string name="power_utterance" msgid="7444296686402104807">"Սնուցման կոճակի ընտրանքներ"</string>
     <string name="recent_apps_label" msgid="6583276995616385847">"Վերջին հավելվածներ"</string>
     <string name="lockscreen_label" msgid="648347953557887087">"Կողպէկրան"</string>
-    <string name="quick_settings_label" msgid="2999117381487601865">"Արագ\\nկարգավորումներ"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Արագ կարգա­վորումներ"</string>
     <string name="notifications_label" msgid="6829741046963013567">"Ծանուցումներ"</string>
     <string name="screenshot_label" msgid="863978141223970162">"Սքրինշոթ"</string>
     <string name="screenshot_utterance" msgid="1430760563401895074">"Ստանալ սքրինշոթը"</string>
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index c1390b2..dc4208e 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -2,8 +2,7 @@
     name: "com_android_systemui_flags",
     package: "com.android.systemui",
     srcs: [
-        "systemui.aconfig",
-        "accessibility.aconfig",
+        "*.aconfig",
     ],
 }
 
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8841967..08ecf09b 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -1,8 +1,17 @@
 package: "com.android.systemui"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
     name: "floating_menu_overlaps_nav_bars_flag"
     namespace: "accessibility"
     description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
     bug: "283768342"
 }
+
+flag {
+    name: "floating_menu_ime_displacement_animation"
+    namespace: "accessibility"
+    description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
+    bug: "281150010"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
new file mode 100644
index 0000000..2c6ff97
--- /dev/null
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.systemui"
+
+flag {
+    name: "communal_hub"
+    namespace: "communal"
+    description: "Enables the communal hub experience"
+    bug: "304584416"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 18117a8..c26d5f5 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -21,3 +21,33 @@
         "(containing the \"Clear all\" button). Should not bring any behavior changes"
     bug: "293167744"
 }
+
+flag {
+    name: "notifications_icon_container_refactor"
+    namespace: "systemui"
+    description: "Enables the refactored version of the notification icon container in StatusBar, "
+        "AOD, and the notification shelf. Should not bring any behavioral changes."
+    bug: "278765923"
+}
+
+flag {
+    name: "notification_lifetime_extension_refactor"
+    namespace: "systemui"
+    description: "Enables moving notification lifetime extension management from SystemUI to "
+        "Notification Manager Service"
+    bug: "299448097"
+}
+
+flag {
+    name: "notification_async_hybrid_view_inflation"
+    namespace: "systemui"
+    description: "Inflates the hybrid (single-line) notification views form the background thread."
+    bug: "217799515"
+}
+
+flag {
+    name: "scene_container"
+    namespace: "systemui"
+    description: "Enables the scene container framework go/flexiglass."
+    bug: "283121968"
+}
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 4aac279..ab4db45 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -290,9 +290,10 @@
         controller: Controller?,
         animate: Boolean = true,
         packageName: String? = null,
+        showOverLockscreen: Boolean = false,
         intentStarter: PendingIntentStarter
     ) {
-        startIntentWithAnimation(controller, animate, packageName) {
+        startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) {
             intentStarter.startPendingIntent(it)
         }
     }
@@ -893,7 +894,7 @@
                 return
             }
 
-            Log.i(TAG, "Remote animation timed out")
+            Log.wtf(TAG, "Remote animation timed out")
             timedOut = true
 
             if (DEBUG_LAUNCH_ANIMATION) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 6c4b695..af35ea4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -68,6 +68,7 @@
     override var launchContainer = ghostedView.rootView as ViewGroup
     private val launchContainerOverlay: ViewGroupOverlay
         get() = launchContainer.overlay
+
     private val launchContainerLocation = IntArray(2)
 
     /** The ghost view that is drawn and animated instead of the ghosted view. */
@@ -206,9 +207,8 @@
             return
         }
 
-        backgroundView = FrameLayout(launchContainer.context).also {
-            launchContainerOverlay.add(it)
-        }
+        backgroundView =
+            FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -226,6 +226,17 @@
         // the content before fading out the background.
         ghostView = GhostView.addGhost(ghostedView, launchContainer)
 
+        // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
+        // adds it first to a [FrameLayout] container. It then adds _that_ container to an
+        // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently,
+        // however, the only way to get a reference to that overlay is by going through our
+        // [ghostView]. The [OverlayViewGroup] will always be its grandparent view.
+        // TODO(b/306652954) reference the overlay view group directly if we can
+        (ghostView?.parent?.parent as? ViewGroup)?.let {
+            it.clipChildren = false
+            it.clipToPadding = false
+        }
+
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
new file mode 100644
index 0000000..46125be
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.ide.common.blame.SourcePosition
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.time.Year
+import org.jetbrains.uast.UComment
+import org.jetbrains.uast.UFile
+
+/**
+ * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
+ */
+class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler? {
+        return object : UElementHandler() {
+            override fun visitFile(node: UFile) {
+                val firstComment = node.allCommentsInFile.firstOrNull()
+                // Normally we don't need to explicitly handle suppressing case and just return
+                // error as usual with indicating node and lint will ignore it for us. But here
+                // suppressing will be applied on top of comment that doesn't exist so we don't have
+                // node to return - it's a bit of corner case
+                if (firstComment != null && firstComment.isSuppressingComment()) {
+                    return
+                }
+                if (firstComment == null || !firstComment.isLicenseComment()) {
+                    val firstLineOfFile =
+                        Location.create(
+                            context.file,
+                            SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
+                        )
+                    context.report(
+                        issue = ISSUE,
+                        location = firstLineOfFile,
+                        message =
+                            "License header is missing\n" +
+                                "Please add the following copyright and license header to the" +
+                                " beginning of the file:\n\n" +
+                                copyrightHeader
+                    )
+                }
+            }
+        }
+    }
+
+    private fun UComment.isSuppressingComment(): Boolean {
+        val suppressingComment =
+            "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
+        return text.contains(suppressingComment)
+    }
+
+    private fun UComment.isLicenseComment(): Boolean {
+        // We probably don't want to compare full copyright header in case there are some small
+        // discrepancies in already existing files, e.g. year. We could do regexp but it should be
+        // good enough if this detector deals with missing
+        // license header instead of incorrect license header
+        return text.contains("Apache License")
+    }
+
+    private val copyrightHeader: String
+        get() =
+            """
+            /*
+             * Copyright (C) ${Year.now().value} The Android Open Source Project
+             *
+             * Licensed under the Apache License, Version 2.0 (the "License");
+             * you may not use this file except in compliance with the License.
+             * You may obtain a copy of the License at
+             *
+             *     http://www.apache.org/licenses/LICENSE-2.0
+             *
+             * Unless required by applicable law or agreed to in writing, software
+             * distributed under the License is distributed on an "AS IS" BASIS,
+             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+             * See the License for the specific language governing permissions and
+             * limitations under the License.
+             */
+            """
+                .trimIndent()
+                .trim()
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "MissingApacheLicenseDetector",
+                briefDescription = "File is missing Apache license information",
+                explanation =
+                    """
+                        Every source code file should have copyright and license information \
+                        attached at the beginning.""",
+                category = Category.COMPLIANCE,
+                priority = 8,
+                // ignored by default and then explicitly overridden in SysUI's soong configuration
+                severity = Severity.IGNORE,
+                implementation =
+                    Implementation(MissingApacheLicenseDetector::class.java, Scope.JAVA_FILE_SCOPE),
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 520c888..e09aa42 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -42,6 +42,7 @@
                 StaticSettingsProviderDetector.ISSUE,
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
+                MissingApacheLicenseDetector.ISSUE,
             )
 
     override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
new file mode 100644
index 0000000..78e133f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import java.time.Year
+import org.junit.Test
+
+class MissingApacheLicenseDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector {
+        return MissingApacheLicenseDetector()
+    }
+
+    override fun getIssues(): List<Issue> {
+        return listOf(
+            MissingApacheLicenseDetector.ISSUE,
+        )
+    }
+
+    @Test
+    fun testHasCopyright() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    /*
+                     * Copyright (C) ${Year.now().value} The Android Open Source Project
+                     *
+                     * Licensed under the Apache License, Version 2.0 (the "License");
+                     * you may not use this file except in compliance with the License.
+                     * You may obtain a copy of the License at
+                     *
+                     *     http://www.apache.org/licenses/LICENSE-2.0
+                     *
+                     * Unless required by applicable law or agreed to in writing, software
+                     * distributed under the License is distributed on an "AS IS" BASIS,
+                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+                     * See the License for the specific language governing permissions and
+                     * limitations under the License.
+                     */
+
+                    package test.pkg.name
+
+                    class MyTest
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(MissingApacheLicenseDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDoesntHaveCopyright() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    package test.pkg.name
+
+                    class MyTest
+                    """
+                        .trimIndent()
+                )
+            )
+            // skipping mode SUPPRESSIBLE because lint tries to add @Suppress to class which
+            // probably doesn't make much sense for license header (which is far above it) and for
+            // kotlin files that can have several classes. If someone really wants to omit header
+            // they can do it with //noinspection
+            .skipTestModes(TestMode.SUPPRESSIBLE)
+            .issues(MissingApacheLicenseDetector.ISSUE)
+            .run()
+            .expectContains("License header is missing")
+    }
+}
diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp
new file mode 100644
index 0000000..88dad66
--- /dev/null
+++ b/packages/SystemUI/communal/layout/Android.bp
@@ -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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "CommunalLayoutLib",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.arch.core_core-runtime",
+        "androidx.compose.animation_animation-graphics",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+        "jsr330",
+        "kotlinx-coroutines-android",
+        "kotlinx-coroutines-core",
+    ],
+    manifest: "AndroidManifest.xml",
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml
new file mode 100644
index 0000000..141be07
--- /dev/null
+++ b/packages/SystemUI/communal/layout/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<manifest package="com.android.systemui.communal.layout" />
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
new file mode 100644
index 0000000..91fe33c
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.layout
+
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+
+/** Computes the arrangement of cards. */
+class CommunalLayoutEngine {
+    companion object {
+        /**
+         * Determines the size that each card should be rendered in, and distributes the cards into
+         * columns.
+         *
+         * Returns a nested list where the outer list contains columns, and the inner list contains
+         * cards in each column.
+         *
+         * Currently treats the first supported size as the size to be rendered in, ignoring other
+         * supported sizes.
+         *
+         * Cards are ordered by priority, from highest to lowest.
+         */
+        fun distributeCardsIntoColumns(
+            cards: List<CommunalGridLayoutCard>,
+        ): List<List<CommunalGridLayoutCardInfo>> {
+            val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
+
+            var capacityOfLastColumn = 0
+            val sorted = cards.sortedByDescending { it.priority }
+            for (card in sorted) {
+                val cardSize = card.supportedSizes.first()
+                if (capacityOfLastColumn >= cardSize.value) {
+                    // Card fits in last column
+                    capacityOfLastColumn -= cardSize.value
+                } else {
+                    // Create a new column
+                    result.add(arrayListOf())
+                    capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value
+                }
+
+                result.last().add(CommunalGridLayoutCardInfo(card, cardSize))
+            }
+
+            return result
+        }
+    }
+
+    /**
+     * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the
+     * card should be rendered in.
+     */
+    data class CommunalGridLayoutCardInfo(
+        val card: CommunalGridLayoutCard,
+        val size: CommunalGridLayoutCard.Size,
+    )
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
new file mode 100644
index 0000000..33024f7
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.layout.ui.compose
+
+import android.util.SizeF
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.communal.layout.CommunalLayoutEngine
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+
+/**
+ * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size
+ * and follows a specific order based on its priority, ensuring a seamless layout without any gaps.
+ */
+@Composable
+fun CommunalGridLayout(
+    modifier: Modifier,
+    layoutConfig: CommunalGridLayoutConfig,
+    communalCards: List<CommunalGridLayoutCard>,
+) {
+    val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards)
+    LazyRow(
+        modifier = modifier.height(layoutConfig.gridHeight),
+        horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
+    ) {
+        for (column in columns) {
+            item {
+                Column(
+                    modifier = Modifier.width(layoutConfig.cardWidth),
+                    verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
+                ) {
+                    for (cardInfo in column) {
+                        Row(
+                            modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
+                        ) {
+                            cardInfo.card.Content(
+                                modifier = Modifier.fillMaxSize(),
+                                size =
+                                    SizeF(
+                                        layoutConfig.cardWidth.value,
+                                        layoutConfig.cardHeight(cardInfo.size).value,
+                                    ),
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
new file mode 100644
index 0000000..4b2a156
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.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.communal.layout.ui.compose.config
+
+import android.util.SizeF
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/** A card that hosts content to be rendered in the communal grid layout. */
+abstract class CommunalGridLayoutCard {
+    /**
+     * Content to be hosted by the card.
+     *
+     * To host non-Compose views, see
+     * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+     *
+     * @param size The size given to the card. Content of the card should fill all this space, given
+     *   that margins and paddings have been taken care of by the layout.
+     */
+    @Composable abstract fun Content(modifier: Modifier, size: SizeF)
+
+    /**
+     * Sizes supported by the card.
+     *
+     * If multiple sizes are available, they should be ranked in order of preference, from most to
+     * least preferred.
+     */
+    abstract val supportedSizes: List<Size>
+
+    /**
+     * Priority of the content hosted by the card.
+     *
+     * The value of priority is relative to other cards. Cards with a higher priority are generally
+     * ordered first.
+     */
+    open val priority: Int = 0
+
+    /**
+     * Size of the card.
+     *
+     * @param value A numeric value that represents the size. Must be less than or equal to
+     *   [Size.FULL].
+     */
+    enum class Size(val value: Int) {
+        /** The card takes up full height of the grid layout. */
+        FULL(value = 6),
+
+        /** The card takes up half of the vertical space of the grid layout. */
+        HALF(value = 3),
+
+        /** The card takes up a third of the vertical space of the grid layout. */
+        THIRD(value = 2),
+    }
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
new file mode 100644
index 0000000..143df83
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.layout.ui.compose.config
+
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.times
+
+/**
+ * Configurations of the communal grid layout.
+ *
+ * The communal grid layout follows Material Design's responsive layout grid (see
+ * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided
+ * up by columns and gutters, and each card occupies one or multiple columns.
+ */
+data class CommunalGridLayoutConfig(
+    /**
+     * Size in dp of each grid column.
+     *
+     * Every card occupies one or more grid columns, which means that the width of each card is
+     * influenced by the size of the grid columns.
+     */
+    val gridColumnSize: Dp,
+
+    /**
+     * Size in dp of each grid gutter.
+     *
+     * A gutter is the space between columns that helps separate content. This is, therefore, also
+     * the size of the gaps between cards, both horizontally and vertically.
+     */
+    val gridGutter: Dp,
+
+    /**
+     * Height in dp of the grid layout.
+     *
+     * Cards with a full size take up the entire height of the grid layout.
+     */
+    val gridHeight: Dp,
+
+    /**
+     * Number of grid columns that each card occupies.
+     *
+     * It's important to note that all the cards take up the same number of grid columns, or in
+     * simpler terms, they all have the same width.
+     */
+    val gridColumnsPerCard: Int,
+) {
+    /**
+     * Width in dp of each card.
+     *
+     * It's important to note that all the cards take up the same number of grid columns, or in
+     * simpler terms, they all have the same width.
+     */
+    val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1)
+
+    /** Returns the height of a card in dp, based on its size. */
+    fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp {
+        return when (cardSize) {
+            CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1)
+            CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2)
+            CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3)
+        }
+    }
+
+    /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */
+    private fun cardHeightBy(denominator: Int): Dp {
+        return (gridHeight - (denominator - 1) * gridGutter) / denominator
+    }
+}
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
new file mode 100644
index 0000000..9a05504
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+    name: "CommunalLayoutLibTests",
+    srcs: [
+        "**/*.kt",
+    ],
+    static_libs: [
+        "CommunalLayoutLib",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "junit",
+        "kotlinx_coroutines_test",
+        "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
+        "testables",
+        "truth",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    manifest: "AndroidManifest.xml",
+}
diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b19007c
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.communal.layout.tests">
+
+    <application android:debuggable="true" android:largeHeap="true">
+        <uses-library android:name="android.test.mock" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.testing.TestableInstrumentation"
+        android:targetPackage="com.android.systemui.communal.layout.tests"
+        android:label="Tests for CommunalLayoutLib">
+    </instrumentation>
+
+</manifest>
diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
new file mode 100644
index 0000000..1352b23
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration description="Runs tests for CommunalLayoutLib">
+
+    <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="CommunalLayoutLibTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="CommunalLayoutLibTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.systemui.communal.layout.tests" />
+        <option name="runner" value="android.testing.TestableInstrumentation" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+</configuration>
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
new file mode 100644
index 0000000..50b7c5f
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -0,0 +1,145 @@
+package com.android.systemui.communal.layout
+
+import android.util.SizeF
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalLayoutEngineTest {
+    @Test
+    fun distribution_fullLayout() {
+        val cards =
+            listOf(
+                generateCard(CommunalGridLayoutCard.Size.FULL),
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+            )
+        val expected =
+            listOf(
+                listOf(
+                    CommunalGridLayoutCard.Size.FULL,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.HALF,
+                    CommunalGridLayoutCard.Size.HALF,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.THIRD,
+                    CommunalGridLayoutCard.Size.THIRD,
+                    CommunalGridLayoutCard.Size.THIRD,
+                ),
+            )
+
+        assertDistributionBySize(cards, expected)
+    }
+
+    @Test
+    fun distribution_layoutWithGaps() {
+        val cards =
+            listOf(
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.FULL),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+            )
+        val expected =
+            listOf(
+                listOf(
+                    CommunalGridLayoutCard.Size.HALF,
+                    CommunalGridLayoutCard.Size.THIRD,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.HALF,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.FULL,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.THIRD,
+                ),
+            )
+
+        assertDistributionBySize(cards, expected)
+    }
+
+    @Test
+    fun distribution_sortByPriority() {
+        val cards =
+            listOf(
+                generateCard(priority = 2),
+                generateCard(priority = 7),
+                generateCard(priority = 10),
+                generateCard(priority = 1),
+                generateCard(priority = 5),
+            )
+        val expected =
+            listOf(
+                listOf(10, 7),
+                listOf(5, 2),
+                listOf(1),
+            )
+
+        assertDistributionByPriority(cards, expected)
+    }
+
+    private fun assertDistributionBySize(
+        cards: List<CommunalGridLayoutCard>,
+        expected: List<List<CommunalGridLayoutCard.Size>>,
+    ) {
+        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
+
+        for (c in expected.indices) {
+            for (r in expected[c].indices) {
+                assertThat(result[c][r].size).isEqualTo(expected[c][r])
+            }
+        }
+    }
+
+    private fun assertDistributionByPriority(
+        cards: List<CommunalGridLayoutCard>,
+        expected: List<List<Int>>,
+    ) {
+        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
+
+        for (c in expected.indices) {
+            for (r in expected[c].indices) {
+                assertThat(result[c][r].card.priority).isEqualTo(expected[c][r])
+            }
+        }
+    }
+
+    private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
+        return object : CommunalGridLayoutCard() {
+            override val supportedSizes = listOf(size)
+
+            @Composable
+            override fun Content(modifier: Modifier, size: SizeF) {
+                Card(modifier = modifier, content = {})
+            }
+        }
+    }
+
+    private fun generateCard(priority: Int): CommunalGridLayoutCard {
+        return object : CommunalGridLayoutCard() {
+            override val supportedSizes = listOf(Size.HALF)
+            override val priority = priority
+
+            @Composable
+            override fun Content(modifier: Modifier, size: SizeF) {
+                Card(modifier = modifier, content = {})
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
new file mode 100644
index 0000000..946eeec
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
@@ -0,0 +1,63 @@
+package com.android.systemui.communal.layout.ui.compose.config
+
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalGridLayoutConfigTest {
+    @Test
+    fun cardWidth() {
+        Truth.assertThat(
+                CommunalGridLayoutConfig(
+                        gridColumnSize = 5.dp,
+                        gridGutter = 3.dp,
+                        gridHeight = 17.dp,
+                        gridColumnsPerCard = 1,
+                    )
+                    .cardWidth
+            )
+            .isEqualTo(5.dp)
+
+        Truth.assertThat(
+                CommunalGridLayoutConfig(
+                        gridColumnSize = 5.dp,
+                        gridGutter = 3.dp,
+                        gridHeight = 17.dp,
+                        gridColumnsPerCard = 2,
+                    )
+                    .cardWidth
+            )
+            .isEqualTo(13.dp)
+
+        Truth.assertThat(
+                CommunalGridLayoutConfig(
+                        gridColumnSize = 5.dp,
+                        gridGutter = 3.dp,
+                        gridHeight = 17.dp,
+                        gridColumnsPerCard = 3,
+                    )
+                    .cardWidth
+            )
+            .isEqualTo(21.dp)
+    }
+
+    @Test
+    fun cardHeight() {
+        val config =
+            CommunalGridLayoutConfig(
+                gridColumnSize = 5.dp,
+                gridGutter = 2.dp,
+                gridHeight = 10.dp,
+                gridColumnsPerCard = 3,
+            )
+
+        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp)
+        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp)
+        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 17c74ba..0e7694e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -315,7 +315,8 @@
                 }
             }
 
-            override fun shouldAnimateExit(): Boolean = isComposed.value
+            override fun shouldAnimateExit(): Boolean =
+                isComposed.value && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown
 
             override fun onExitAnimationCancelled() {
                 isDialogShowing.value = false
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
deleted file mode 100644
index e275fca..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ /dev/null
@@ -1,662 +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 com.android.compose.animation.scene
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
-import androidx.compose.foundation.gestures.DraggableState
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.draggable
-import androidx.compose.foundation.gestures.rememberDraggableState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.dp
-import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection
-import kotlin.math.absoluteValue
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-/**
- * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
- */
-@Composable
-internal fun Modifier.swipeToScene(
-    layoutImpl: SceneTransitionLayoutImpl,
-    orientation: Orientation,
-): Modifier {
-    val state = layoutImpl.state.transitionState
-    val currentScene = layoutImpl.scene(state.currentScene)
-    val transition = remember {
-        // Note that the currentScene here does not matter, it's only used for initializing the
-        // transition and will be replaced when a drag event starts.
-        SwipeTransition(initialScene = currentScene)
-    }
-
-    val enabled = state == transition || currentScene.shouldEnableSwipes(orientation)
-
-    // Immediately start the drag if this our [transition] is currently animating to a scene (i.e.
-    // the user released their input pointer after swiping in this orientation) and the user can't
-    // swipe in the other direction.
-    val startDragImmediately =
-        state == transition &&
-            !transition.isUserInputOngoing &&
-            !currentScene.shouldEnableSwipes(orientation.opposite())
-
-    // The velocity threshold at which the intent of the user is to swipe up or down. It is the same
-    // as SwipeableV2Defaults.VelocityThreshold.
-    val velocityThreshold = with(LocalDensity.current) { 125.dp.toPx() }
-
-    // The positional threshold at which the intent of the user is to swipe to the next scene. It is
-    // the same as SwipeableV2Defaults.PositionalThreshold.
-    val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
-
-    val draggableState = rememberDraggableState { delta ->
-        onDrag(layoutImpl, transition, orientation, delta)
-    }
-
-    return nestedScroll(
-            connection =
-                rememberSwipeToSceneNestedScrollConnection(
-                    orientation = orientation,
-                    coroutineScope = rememberCoroutineScope(),
-                    draggableState = draggableState,
-                    transition = transition,
-                    layoutImpl = layoutImpl,
-                    velocityThreshold = velocityThreshold,
-                    positionalThreshold = positionalThreshold
-                ),
-        )
-        .draggable(
-            state = draggableState,
-            orientation = orientation,
-            enabled = enabled,
-            startDragImmediately = startDragImmediately,
-            onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
-            onDragStopped = { velocity ->
-                onDragStopped(
-                    layoutImpl = layoutImpl,
-                    transition = transition,
-                    velocity = velocity,
-                    velocityThreshold = velocityThreshold,
-                    positionalThreshold = positionalThreshold,
-                )
-            },
-        )
-}
-
-private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
-    var _currentScene by mutableStateOf(initialScene)
-    override val currentScene: SceneKey
-        get() = _currentScene.key
-
-    var _fromScene by mutableStateOf(initialScene)
-    override val fromScene: SceneKey
-        get() = _fromScene.key
-
-    var _toScene by mutableStateOf(initialScene)
-    override val toScene: SceneKey
-        get() = _toScene.key
-
-    override val progress: Float
-        get() {
-            val offset = if (isUserInputOngoing) dragOffset else offsetAnimatable.value
-            if (distance == 0f) {
-                // This can happen only if fromScene == toScene.
-                error(
-                    "Transition.progress should be called only when Transition.fromScene != " +
-                        "Transition.toScene"
-                )
-            }
-            return offset / distance
-        }
-
-    override val isInitiatedByUserInput = true
-
-    var _isUserInputOngoing by mutableStateOf(false)
-    override val isUserInputOngoing: Boolean
-        get() = _isUserInputOngoing
-
-    /** The current offset caused by the drag gesture. */
-    var dragOffset by mutableFloatStateOf(0f)
-
-    /** The animatable used to animate the offset once the user lifted its finger. */
-    val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)
-
-    /** Job to check that there is at most one offset animation in progress. */
-    private var offsetAnimationJob: Job? = null
-
-    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
-    fun startOffsetAnimation(job: () -> Job) {
-        stopOffsetAnimation()
-        offsetAnimationJob = job()
-    }
-
-    /** Stops any ongoing offset animation. */
-    fun stopOffsetAnimation() {
-        offsetAnimationJob?.cancel()
-    }
-
-    /** The absolute distance between [fromScene] and [toScene]. */
-    var absoluteDistance = 0f
-
-    /**
-     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
-     * or to the left of [toScene].
-     */
-    var _distance by mutableFloatStateOf(0f)
-    val distance: Float
-        get() = _distance
-}
-
-/** The destination scene when swiping up or left from [this@upOrLeft]. */
-private fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
-    return when (orientation) {
-        Orientation.Vertical -> userActions[Swipe.Up]
-        Orientation.Horizontal -> userActions[Swipe.Left]
-    }
-}
-
-/** The destination scene when swiping down or right from [this@downOrRight]. */
-private fun Scene.downOrRight(orientation: Orientation): SceneKey? {
-    return when (orientation) {
-        Orientation.Vertical -> userActions[Swipe.Down]
-        Orientation.Horizontal -> userActions[Swipe.Right]
-    }
-}
-
-/** Whether swipe should be enabled in the given [orientation]. */
-private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
-    return upOrLeft(orientation) != null || downOrRight(orientation) != null
-}
-
-private fun Orientation.opposite(): Orientation {
-    return when (this) {
-        Orientation.Vertical -> Orientation.Horizontal
-        Orientation.Horizontal -> Orientation.Vertical
-    }
-}
-
-private fun onDragStarted(
-    layoutImpl: SceneTransitionLayoutImpl,
-    transition: SwipeTransition,
-    orientation: Orientation,
-) {
-    transition._isUserInputOngoing = true
-
-    if (layoutImpl.state.transitionState == transition) {
-        // This [transition] was already driving the animation: simply take over it.
-        if (!transition.isUserInputOngoing) {
-            // Stop animating and start from where the current offset. Setting the animation job to
-            // `null` will effectively cancel the animation.
-            transition.stopOffsetAnimation()
-            transition.dragOffset = transition.offsetAnimatable.value
-        }
-
-        return
-    }
-
-    // TODO(b/290184746): Better handle interruptions here if state != idle.
-
-    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
-
-    transition._currentScene = fromScene
-    transition._fromScene = fromScene
-
-    // We don't know where we are transitioning to yet given that the drag just started, so set it
-    // to fromScene, which will effectively be treated the same as Idle(fromScene).
-    transition._toScene = fromScene
-
-    transition.stopOffsetAnimation()
-    transition.dragOffset = 0f
-
-    // Use the layout size in the swipe orientation for swipe distance.
-    // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
-    // will also have to make sure that we correctly handle overscroll.
-    transition.absoluteDistance =
-        when (orientation) {
-            Orientation.Horizontal -> layoutImpl.size.width
-            Orientation.Vertical -> layoutImpl.size.height
-        }.toFloat()
-
-    if (transition.absoluteDistance > 0f) {
-        layoutImpl.state.transitionState = transition
-    }
-}
-
-private fun onDrag(
-    layoutImpl: SceneTransitionLayoutImpl,
-    transition: SwipeTransition,
-    orientation: Orientation,
-    delta: Float,
-) {
-    transition.dragOffset += delta
-
-    // First check transition.fromScene should be changed for the case where the user quickly swiped
-    // twice in a row to accelerate the transition and go from A => B then B => C really fast.
-    maybeHandleAcceleratedSwipe(transition, orientation)
-
-    val offset = transition.dragOffset
-    val fromScene = transition._fromScene
-
-    // Compute the target scene depending on the current offset.
-    val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl)
-
-    if (transition._toScene.key != target.sceneKey) {
-        transition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
-    }
-
-    if (transition._distance != target.distance) {
-        transition._distance = target.distance
-    }
-}
-
-/**
- * Change fromScene in the case where the user quickly swiped multiple times in the same direction
- * to accelerate the transition from A => B then B => C.
- */
-private fun maybeHandleAcceleratedSwipe(
-    transition: SwipeTransition,
-    orientation: Orientation,
-) {
-    val toScene = transition._toScene
-    val fromScene = transition._fromScene
-
-    // If the swipe was not committed, don't do anything.
-    if (fromScene == toScene || transition._currentScene != toScene) {
-        return
-    }
-
-    // If the offset is past the distance then let's change fromScene so that the user can swipe to
-    // the next screen or go back to the previous one.
-    val offset = transition.dragOffset
-    val absoluteDistance = transition.absoluteDistance
-    if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
-        transition.dragOffset += absoluteDistance
-        transition._fromScene = toScene
-    } else if (offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key) {
-        transition.dragOffset -= absoluteDistance
-        transition._fromScene = toScene
-    }
-
-    // Important note: toScene and distance will be updated right after this function is called,
-    // using fromScene and dragOffset.
-}
-
-private data class TargetScene(
-    val sceneKey: SceneKey,
-    val distance: Float,
-)
-
-private fun Scene.findTargetSceneAndDistance(
-    orientation: Orientation,
-    directionOffset: Float,
-    layoutImpl: SceneTransitionLayoutImpl,
-): TargetScene {
-    val maxDistance =
-        when (orientation) {
-            Orientation.Horizontal -> layoutImpl.size.width
-            Orientation.Vertical -> layoutImpl.size.height
-        }.toFloat()
-
-    val upOrLeft = upOrLeft(orientation)
-    val downOrRight = downOrRight(orientation)
-
-    // Compute the target scene depending on the current offset.
-    return when {
-        directionOffset < 0f && upOrLeft != null -> {
-            TargetScene(
-                sceneKey = upOrLeft,
-                distance = -maxDistance,
-            )
-        }
-        directionOffset > 0f && downOrRight != null -> {
-            TargetScene(
-                sceneKey = downOrRight,
-                distance = maxDistance,
-            )
-        }
-        else -> {
-            TargetScene(
-                sceneKey = key,
-                distance = 0f,
-            )
-        }
-    }
-}
-
-private fun CoroutineScope.onDragStopped(
-    layoutImpl: SceneTransitionLayoutImpl,
-    transition: SwipeTransition,
-    velocity: Float,
-    velocityThreshold: Float,
-    positionalThreshold: Float,
-    canChangeScene: Boolean = true,
-) {
-    // The state was changed since the drag started; don't do anything.
-    if (layoutImpl.state.transitionState != transition) {
-        return
-    }
-
-    // We were not animating.
-    if (transition._fromScene == transition._toScene) {
-        layoutImpl.state.transitionState = TransitionState.Idle(transition._fromScene.key)
-        return
-    }
-
-    // Compute the destination scene (and therefore offset) to settle in.
-    val targetScene: Scene
-    val targetOffset: Float
-    val offset = transition.dragOffset
-    val distance = transition.distance
-    if (
-        canChangeScene &&
-            shouldCommitSwipe(
-                offset,
-                distance,
-                velocity,
-                velocityThreshold,
-                positionalThreshold,
-                wasCommitted = transition._currentScene == transition._toScene,
-            )
-    ) {
-        targetOffset = distance
-        targetScene = transition._toScene
-    } else {
-        targetOffset = 0f
-        targetScene = transition._fromScene
-    }
-
-    // If the effective current scene changed, it should be reflected right now in the current scene
-    // state, even before the settle animation is ongoing. That way all the swipeables and back
-    // handlers will be refreshed and the user can for instance quickly swipe vertically from A => B
-    // then horizontally from B => C, or swipe from A => B then immediately go back B => A.
-    if (targetScene != transition._currentScene) {
-        transition._currentScene = targetScene
-        layoutImpl.onChangeScene(targetScene.key)
-    }
-
-    animateOffset(
-        transition = transition,
-        layoutImpl = layoutImpl,
-        initialVelocity = velocity,
-        targetOffset = targetOffset,
-        targetScene = targetScene.key
-    )
-}
-
-/**
- * Whether the swipe to the target scene should be committed or not. This is inspired by
- * SwipeableV2.computeTarget().
- */
-private fun shouldCommitSwipe(
-    offset: Float,
-    distance: Float,
-    velocity: Float,
-    velocityThreshold: Float,
-    positionalThreshold: Float,
-    wasCommitted: Boolean,
-): Boolean {
-    fun isCloserToTarget(): Boolean {
-        return (offset - distance).absoluteValue < offset.absoluteValue
-    }
-
-    // Swiping up or left.
-    if (distance < 0f) {
-        return if (offset > 0f || velocity >= velocityThreshold) {
-            false
-        } else {
-            velocity <= -velocityThreshold ||
-                (offset <= -positionalThreshold && !wasCommitted) ||
-                isCloserToTarget()
-        }
-    }
-
-    // Swiping down or right.
-    return if (offset < 0f || velocity <= -velocityThreshold) {
-        false
-    } else {
-        velocity >= velocityThreshold ||
-            (offset >= positionalThreshold && !wasCommitted) ||
-            isCloserToTarget()
-    }
-}
-
-private fun CoroutineScope.animateOffset(
-    transition: SwipeTransition,
-    layoutImpl: SceneTransitionLayoutImpl,
-    initialVelocity: Float,
-    targetOffset: Float,
-    targetScene: SceneKey,
-) {
-    transition.startOffsetAnimation {
-        launch {
-            if (transition.isUserInputOngoing) {
-                transition.offsetAnimatable.snapTo(transition.dragOffset)
-            }
-            transition._isUserInputOngoing = false
-
-            transition.offsetAnimatable.animateTo(
-                targetOffset,
-                // TODO(b/290184746): Make this spring spec configurable.
-                spring(
-                    stiffness = Spring.StiffnessMediumLow,
-                    visibilityThreshold = OffsetVisibilityThreshold
-                ),
-                initialVelocity = initialVelocity,
-            )
-
-            // Now that the animation is done, the state should be idle. Note that if the state
-            // was changed since this animation started, some external code changed it and we
-            // shouldn't do anything here. Note also that this job will be cancelled in the case
-            // where the user intercepts this swipe.
-            if (layoutImpl.state.transitionState == transition) {
-                layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
-            }
-        }
-    }
-}
-
-private fun CoroutineScope.animateOverscroll(
-    layoutImpl: SceneTransitionLayoutImpl,
-    transition: SwipeTransition,
-    velocity: Velocity,
-    orientation: Orientation,
-): Velocity {
-    val velocityAmount =
-        when (orientation) {
-            Orientation.Vertical -> velocity.y
-            Orientation.Horizontal -> velocity.x
-        }
-
-    if (velocityAmount == 0f) {
-        // There is no remaining velocity
-        return Velocity.Zero
-    }
-
-    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
-    val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl)
-    val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
-
-    if (!isValidTarget || layoutImpl.state.transitionState == transition) {
-        // We have not found a valid target or we are already in a transition
-        return Velocity.Zero
-    }
-
-    transition._currentScene = fromScene
-    transition._fromScene = fromScene
-    transition._toScene = layoutImpl.scene(target.sceneKey)
-    transition._distance = target.distance
-    transition.absoluteDistance = target.distance.absoluteValue
-    transition.stopOffsetAnimation()
-    transition.dragOffset = 0f
-
-    layoutImpl.state.transitionState = transition
-
-    animateOffset(
-        transition = transition,
-        layoutImpl = layoutImpl,
-        initialVelocity = velocityAmount,
-        targetOffset = 0f,
-        targetScene = fromScene.key
-    )
-
-    // The animateOffset animation consumes any remaining velocity.
-    return velocity
-}
-
-/**
- * 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
-
-@Composable
-private fun rememberSwipeToSceneNestedScrollConnection(
-    orientation: Orientation,
-    coroutineScope: CoroutineScope,
-    draggableState: DraggableState,
-    transition: SwipeTransition,
-    layoutImpl: SceneTransitionLayoutImpl,
-    velocityThreshold: Float,
-    positionalThreshold: Float,
-): PriorityPostNestedScrollConnection {
-    val density = LocalDensity.current
-    val scrollConnection =
-        remember(
-            orientation,
-            coroutineScope,
-            draggableState,
-            transition,
-            layoutImpl,
-            velocityThreshold,
-            positionalThreshold,
-            density,
-        ) {
-            fun Offset.toAmount() =
-                when (orientation) {
-                    Orientation.Horizontal -> x
-                    Orientation.Vertical -> y
-                }
-
-            fun Velocity.toAmount() =
-                when (orientation) {
-                    Orientation.Horizontal -> x
-                    Orientation.Vertical -> y
-                }
-
-            fun Float.toOffset() =
-                when (orientation) {
-                    Orientation.Horizontal -> Offset(x = this, y = 0f)
-                    Orientation.Vertical -> Offset(x = 0f, y = this)
-                }
-
-            // The next potential scene is calculated during the canStart
-            var nextScene: SceneKey? = null
-
-            // This is the scene on which we will have priority during the scroll gesture.
-            var priorityScene: SceneKey? = null
-
-            // If we performed a long gesture before entering priority mode, we would have to avoid
-            // moving on to the next scene.
-            var gestureStartedOnNestedChild = false
-
-            PriorityPostNestedScrollConnection(
-                canStart = { offsetAvailable, offsetBeforeStart ->
-                    val amount = offsetAvailable.toAmount()
-                    if (amount == 0f) return@PriorityPostNestedScrollConnection false
-
-                    gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
-
-                    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
-                    nextScene =
-                        when {
-                            amount < 0f -> fromScene.upOrLeft(orientation)
-                            amount > 0f -> fromScene.downOrRight(orientation)
-                            else -> null
-                        }
-
-                    nextScene != null
-                },
-                canContinueScroll = { priorityScene == transition._toScene.key },
-                onStart = {
-                    priorityScene = nextScene
-                    onDragStarted(layoutImpl, transition, orientation)
-                },
-                onScroll = { offsetAvailable ->
-                    val amount = offsetAvailable.toAmount()
-
-                    // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture
-                    // is initiated in a nested child.
-
-                    // Appends a new coroutine to attempt to drag by [amount] px. In this case we
-                    // are assuming that the [coroutineScope] is tied to the main thread and that
-                    // calls to [launch] are therefore queued.
-                    coroutineScope.launch { draggableState.drag { dragBy(amount) } }
-
-                    amount.toOffset()
-                },
-                onStop = { velocityAvailable ->
-                    priorityScene = null
-
-                    coroutineScope.onDragStopped(
-                        layoutImpl = layoutImpl,
-                        transition = transition,
-                        velocity = velocityAvailable.toAmount(),
-                        velocityThreshold = velocityThreshold,
-                        positionalThreshold = positionalThreshold,
-                        canChangeScene = !gestureStartedOnNestedChild
-                    )
-
-                    // The onDragStopped animation consumes any remaining velocity.
-                    velocityAvailable
-                },
-                onPostFling = { velocityAvailable ->
-                    // If there is any velocity left, we can try running an overscroll animation
-                    // between scenes.
-                    coroutineScope.animateOverscroll(
-                        layoutImpl = layoutImpl,
-                        transition = transition,
-                        velocity = velocityAvailable,
-                        orientation = orientation
-                    )
-                },
-            )
-        }
-    DisposableEffect(scrollConnection) {
-        onDispose {
-            coroutineScope.launch {
-                // This should ensure that the draggableState is in a consistent state and that it
-                // does not cause any unexpected behavior.
-                scrollConnection.reset()
-            }
-        }
-    }
-    return scrollConnection
-}
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 5b4a8fb..ddd1c67 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
@@ -22,6 +22,7 @@
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 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
@@ -64,6 +65,17 @@
         throwComposeUnavailableError()
     }
 
+    override fun createCommunalView(
+        context: Context,
+        viewModel: CommunalViewModel,
+    ): View {
+        throwComposeUnavailableError()
+    }
+
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): 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/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index ac59989..eeda6c6 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
@@ -30,6 +30,9 @@
 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.CommunalViewModel
 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
@@ -93,6 +96,21 @@
         }
     }
 
+    override fun createCommunalView(
+        context: Context,
+        viewModel: CommunalViewModel,
+    ): View {
+        return ComposeView(context).apply {
+            setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
+        }
+    }
+
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+        return ComposeView(context).apply {
+            setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
+        }
+    }
+
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private fun displayCutoutFromWindowInsets(
         scope: CoroutineScope,
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index e4426fe..16c2437 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,8 +31,10 @@
     ],
 
     static_libs: [
+        "CommunalLayoutLib",
         "SystemUI-core",
         "PlatformComposeCore",
+        "PlatformComposeSceneTransitionLayout",
 
         "androidx.compose.runtime_runtime",
         "androidx.compose.animation_animation-graphics",
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
new file mode 100644
index 0000000..46d418a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -0,0 +1,123 @@
+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.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.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.ElementKey
+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.Swipe
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+object Scenes {
+    val Blank = SceneKey(name = "blank")
+    val Communal = SceneKey(name = "communal")
+}
+
+object Communal {
+    object Elements {
+        val Content = ElementKey("CommunalContent")
+    }
+}
+
+val sceneTransitions = transitions {
+    from(Scenes.Blank, to = Scenes.Communal) {
+        spec = tween(durationMillis = 500)
+
+        translate(Communal.Elements.Content, Edge.Right)
+        fade(Communal.Elements.Content)
+    }
+}
+
+/**
+ * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions.
+ *
+ * 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.
+ */
+@Composable
+fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
+    val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+
+    // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
+    var showSceneTransitionLayout by remember { mutableStateOf(true) }
+    if (!showSceneTransitionLayout) {
+        return
+    }
+
+    SceneTransitionLayout(
+        modifier = modifier.fillMaxSize(),
+        currentScene = currentScene,
+        onChangeScene = setCurrentScene,
+        transitions = sceneTransitions,
+    ) {
+        scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+            BlankScene { showSceneTransitionLayout = false }
+        }
+
+        scene(
+            Scenes.Communal,
+            userActions = mapOf(Swipe.Right to Scenes.Blank),
+        ) {
+            CommunalScene(viewModel, modifier = modifier)
+        }
+    }
+}
+
+/**
+ * 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(100.dp)
+                .align(Alignment.CenterEnd)
+                .background(Color(0x55e9f2eb)),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Default scene")
+
+            IconButton(onClick = hideSceneTransitionLayout) {
+                Icon(Icons.Filled.Close, contentDescription = "Close button")
+            }
+        }
+    }
+}
+
+/** Scene containing the glanceable hub UI. */
+@Composable
+private fun SceneScope.CommunalScene(
+    viewModel: CommunalViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+}
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
new file mode 100644
index 0000000..b8fb264
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -0,0 +1,101 @@
+package com.android.systemui.communal.ui.compose
+
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
+
+@Composable
+fun CommunalHub(
+    modifier: Modifier = Modifier,
+    viewModel: CommunalViewModel,
+) {
+    val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
+    val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
+    Box(
+        modifier = modifier.fillMaxSize().background(Color.White),
+    ) {
+        CommunalGridLayout(
+            modifier = Modifier.align(Alignment.CenterStart),
+            layoutConfig =
+                CommunalGridLayoutConfig(
+                    gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
+                    gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
+                    gridHeight = dimensionResource(R.dimen.communal_grid_height),
+                    gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
+                ),
+            communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
+        )
+    }
+}
+
+private val tutorialContent =
+    listOf(
+        tutorialCard(CommunalGridLayoutCard.Size.FULL),
+        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+        tutorialCard(CommunalGridLayoutCard.Size.HALF),
+        tutorialCard(CommunalGridLayoutCard.Size.HALF),
+        tutorialCard(CommunalGridLayoutCard.Size.HALF),
+        tutorialCard(CommunalGridLayoutCard.Size.HALF),
+    )
+
+private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
+    return object : CommunalGridLayoutCard() {
+        override val supportedSizes = listOf(size)
+
+        @Composable
+        override fun Content(modifier: Modifier, size: SizeF) {
+            Card(modifier = modifier, content = {})
+        }
+    }
+}
+
+private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
+    return object : CommunalGridLayoutCard() {
+        override val supportedSizes = listOf(convertToCardSize(model.size))
+        override val priority = model.priority
+
+        @Composable
+        override fun Content(modifier: Modifier, size: SizeF) {
+            AndroidView(
+                modifier = modifier,
+                factory = {
+                    model.view.apply {
+                        if (this is AppWidgetHostView) {
+                            updateAppWidgetSize(Bundle(), listOf(size))
+                        }
+                    }
+                },
+            )
+        }
+    }
+}
+
+private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
+    return when (size) {
+        CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
+        CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
+        CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 0d2ba28..f3bef7b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -16,15 +16,10 @@
 
 package com.android.systemui.communal.ui.compose
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
@@ -38,7 +33,11 @@
 
 /** The communal scene shows glanceable hub when the device is locked and docked. */
 @SysUISingleton
-class CommunalScene @Inject constructor() : ComposableScene {
+class CommunalScene
+@Inject
+constructor(
+    private val viewModel: CommunalViewModel,
+) : ComposableScene {
     override val key = SceneKey.Communal
 
     override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
@@ -51,13 +50,6 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(
-            modifier = modifier.fillMaxSize().background(Color.White),
-        ) {
-            Text(
-                modifier = Modifier.align(Alignment.Center),
-                text = "Hello Communal!",
-            )
-        }
+        CommunalHub(modifier, viewModel)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index e12b7ea..73cb72c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -47,10 +47,10 @@
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.res.R
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.res.R
 
 /**
  * Compose the screen associated to a [PeopleViewModel].
@@ -86,9 +86,9 @@
         modifier = Modifier.fillMaxSize(),
     ) {
         if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
-            PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked)
+            PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel.onTileClicked)
         } else {
-            PeopleScreenEmpty(viewModel::onUserJourneyCancelled)
+            PeopleScreenEmpty(viewModel.onUserJourneyCancelled)
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 689a0a2..eb71490 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -46,7 +46,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.layout
@@ -69,7 +68,6 @@
 import com.android.compose.modifiers.background
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.theme.colorAttr
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
@@ -78,6 +76,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
 /** The Quick Settings footer actions row. */
@@ -89,8 +88,7 @@
 ) {
     val context = LocalContext.current
 
-    // Collect visibility and alphas as soon as we are composed, even when not visible.
-    val isVisible by viewModel.isVisible.collectAsState()
+    // Collect alphas as soon as we are composed, even when not visible.
     val alpha by viewModel.alpha.collectAsState()
     val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
 
@@ -142,11 +140,6 @@
         modifier
             .fillMaxWidth()
             .graphicsLayer { this.alpha = alpha }
-            .drawWithContent {
-                if (isVisible) {
-                    drawContent()
-                }
-            }
             .then(backgroundModifier)
             .padding(
                 top = dimensionResource(R.dimen.qs_footer_actions_top_padding),
@@ -328,7 +321,7 @@
         shape = CircleShape,
         color = colorAttr(R.attr.underSurface),
         contentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
-        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)),
+        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)),
         modifier = modifier.padding(horizontal = 4.dp),
         onClick = onClick,
     ) {
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 0da562b..2e93a09 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
@@ -161,8 +161,7 @@
                 fromScene = fromScene.toModel().key,
                 toScene = toScene.toModel().key,
                 progress = progress,
-                isInitiatedByUserInput = isInitiatedByUserInput,
-                isUserInputOngoing = isUserInputOngoing,
+                isUserInputDriven = isUserInputDriven,
             )
     }
 }
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 591fa76..4bbb78b 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
@@ -371,7 +371,8 @@
             val iconContainer = StatusIconContainer(context, null)
             val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS)
             iconManager.setTint(
-                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary),
+                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse),
             )
             statusBarIconController.addIconGroup(iconManager)
 
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
new file mode 100644
index 0000000..050d1d5
--- /dev/null
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "PlatformComposeSceneTransitionLayout",
+    manifest: "AndroidManifest.xml",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+    use_resource_processor: true,
+}
diff --git a/tests/notification/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml
similarity index 61%
rename from tests/notification/AndroidManifest.xml
rename to packages/SystemUI/compose/scene/AndroidManifest.xml
index 7cee00a7..81131bb 100644
--- a/tests/notification/AndroidManifest.xml
+++ b/packages/SystemUI/compose/scene/AndroidManifest.xml
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!--
+     Copyright (C) 2023 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
-  
+
           http://www.apache.org/licenses/LICENSE-2.0
-  
+
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,14 +16,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.frameworks.tests.notification"
-    >
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
+    package="com.android.compose.animation.scene">
 
-    <instrumentation
-        android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.android.frameworks.tests.notification"
-        android:label="Frameworks Notification Tests" />
+
 </manifest>
diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS
new file mode 100644
index 0000000..33a59c2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+omarmt@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
new file mode 100644
index 0000000..f644a23
--- /dev/null
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+  "presubmit": [
+    {
+      "name": "PlatformComposeSceneTransitionLayoutTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "PlatformComposeCoreTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "SystemUIComposeFeaturesTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "SystemUIComposeGalleryTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
similarity index 71%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 566967f..041fc48 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -17,12 +17,10 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.DisposableEffectResult
-import androidx.compose.runtime.DisposableEffectScope
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.unit.Dp
@@ -45,6 +43,20 @@
 }
 
 /**
+ * Animate a shared Int value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedIntAsState(
+    value: Int,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Int> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Float value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -60,6 +72,20 @@
 }
 
 /**
+ * Animate a shared Float value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedFloatAsState(
+    value: Float,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Float> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Dp value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -75,6 +101,20 @@
 }
 
 /**
+ * Animate a shared Dp value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedDpAsState(
+    value: Dp,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Dp> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Color value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -88,6 +128,19 @@
     return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
 }
 
+/**
+ * Animate a shared Color value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedColorAsState(
+    value: Color,
+    debugName: String,
+): State<Color> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false)
+}
+
 @Composable
 internal fun <T> animateSharedValueAsState(
     layoutImpl: SceneTransitionLayoutImpl,
@@ -98,33 +151,22 @@
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
 ): State<T> {
-    val sharedValue = remember(key) { Element.SharedValue(key, value) }
+    val sharedValue =
+        Snapshot.withoutReadObservation {
+            element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) {
+                Element.SharedValue(key, value)
+            } as Element.SharedValue<T>
+        }
+
     if (value != sharedValue.value) {
         sharedValue.value = value
     }
 
-    DisposableEffect(element, scene, sharedValue) {
-        addSharedValueToElement(element, scene, sharedValue)
-    }
-
     return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
         derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
     }
 }
 
-private fun <T> DisposableEffectScope.addSharedValueToElement(
-    element: Element,
-    scene: Scene,
-    sharedValue: Element.SharedValue<T>,
-): DisposableEffectResult {
-    val sceneValues =
-        element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
-    val sharedValues = sceneValues.sharedValues
-
-    sharedValues[sharedValue.key] = sharedValue
-    return onDispose { sharedValues.remove(sharedValue.key) }
-}
-
 private fun <T> computeValue(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
similarity index 90%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 60c3fd3..88944f10 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -108,7 +108,7 @@
 ) {
     val fromScene = layoutImpl.state.transitionState.currentScene
     val isUserInput =
-        (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
+        (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven
             ?: false
 
     val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec
@@ -119,23 +119,9 @@
     val targetProgress = if (reversed) 0f else 1f
     val transition =
         if (reversed) {
-            OneOffTransition(
-                fromScene = target,
-                toScene = fromScene,
-                currentScene = target,
-                isUserInput,
-                isUserInputOngoing = false,
-                animatable,
-            )
+            OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable)
         } else {
-            OneOffTransition(
-                fromScene = fromScene,
-                toScene = target,
-                currentScene = target,
-                isUserInput,
-                isUserInputOngoing = false,
-                animatable,
-            )
+            OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable)
         }
 
     // Change the current layout state to use this new transition.
@@ -156,8 +142,7 @@
     override val fromScene: SceneKey,
     override val toScene: SceneKey,
     override val currentScene: SceneKey,
-    override val isInitiatedByUserInput: Boolean,
-    override val isUserInputOngoing: Boolean,
+    override val isUserInputDriven: Boolean,
     private val animatable: Animatable<Float, AnimationVector1D>,
 ) : TransitionState.Transition {
     override val progress: Float
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
similarity index 89%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 3bcd920f..abc62c4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -21,6 +21,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -42,6 +43,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
 import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.util.lerp
 
@@ -59,6 +61,17 @@
     /** The mapping between a scene and the values/state this element has in that scene, if any. */
     val sceneValues = SnapshotStateMap<SceneKey, TargetValues>()
 
+    /**
+     * The movable content of this element, if this element is composed using
+     * [SceneScope.MovableElement].
+     */
+    val movableContent by
+        // This is only accessed from the composition (main) thread, so no need to use the default
+        // lock of lazy {} to synchronize.
+        lazy(mode = LazyThreadSafetyMode.NONE) {
+            movableContentOf { content: @Composable () -> Unit -> content() }
+        }
+
     override fun toString(): String {
         return "Element(key=$key)"
     }
@@ -184,29 +197,44 @@
             state.fromScene == state.toScene ||
             !layoutImpl.isTransitionReady(state) ||
             state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues ||
-            !isSharedElementEnabled(layoutImpl, state, element.key)
+            state.toScene !in element.sceneValues
     ) {
         return true
     }
 
-    val otherScene =
-        layoutImpl.scenes.getValue(
-            if (scene.key == state.fromScene) {
-                state.toScene
-            } else {
-                state.fromScene
-            }
-        )
-
-    // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
-    // it is usually drawn below everything else.
-    val isHighestScene = scene.zIndex > otherScene.zIndex
-    return if (element.key.isBackground) {
-        !isHighestScene
-    } else {
-        isHighestScene
+    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+    if (sharedTransformation?.enabled == false) {
+        return true
     }
+
+    return shouldDrawOrComposeSharedElement(
+        layoutImpl,
+        state,
+        scene.key,
+        element.key,
+        sharedTransformation,
+    )
+}
+
+internal fun shouldDrawOrComposeSharedElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    scene: SceneKey,
+    element: ElementKey,
+    sharedTransformation: SharedElementTransformation?
+): Boolean {
+    val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
+
+    return scenePicker.sceneDuringTransition(
+        element = element,
+        fromScene = fromScene,
+        toScene = toScene,
+        progress = transition::progress,
+        fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
+        toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
+    ) == scene
 }
 
 private fun isSharedElementEnabled(
@@ -214,6 +242,14 @@
     transition: TransitionState.Transition,
     element: ElementKey,
 ): Boolean {
+    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    element: ElementKey,
+): SharedElementTransformation? {
     val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
     val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
     val sharedInToScene = spec.transformations(element, transition.toScene).shared
@@ -226,7 +262,7 @@
         )
     }
 
-    return sharedInFromScene?.enabled ?: true
+    return sharedInFromScene
 }
 
 /**
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
new file mode 100644
index 0000000..d005413
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -0,0 +1,20 @@
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import kotlinx.coroutines.CoroutineScope
+
+interface GestureHandler {
+    val draggable: DraggableHandler
+    val nestedScroll: NestedScrollHandler
+}
+
+interface DraggableHandler {
+    suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset)
+    fun onDelta(pixels: Float)
+    suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float)
+}
+
+interface NestedScrollHandler {
+    val connection: NestedScrollConnection
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
similarity index 91%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index b7acc48..bc015ee 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -22,7 +22,7 @@
  * A base class to create unique keys, associated to an [identity] that is used to check the
  * equality of two key instances.
  */
-sealed class Key(val name: String, val identity: Any) {
+sealed class Key(val debugName: String, val identity: Any) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (this.javaClass != other?.javaClass) return false
@@ -34,7 +34,7 @@
     }
 
     override fun toString(): String {
-        return "Key(name=$name)"
+        return "Key(debugName=$debugName)"
     }
 }
 
@@ -49,7 +49,7 @@
     val rootElementKey = ElementKey(name, identity)
 
     override fun toString(): String {
-        return "SceneKey(name=$name)"
+        return "SceneKey(debugName=$debugName)"
     }
 }
 
@@ -71,7 +71,7 @@
     }
 
     override fun toString(): String {
-        return "ElementKey(name=$name)"
+        return "ElementKey(debugName=$debugName)"
     }
 
     companion object {
@@ -89,6 +89,6 @@
 /** Key for a shared value of an element. */
 class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) {
     override fun toString(): String {
-        return "ValueKey(name=$name)"
+        return "ValueKey(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
new file mode 100644
index 0000000..fa385d0
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.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.compose.animation.scene
+
+import android.graphics.Picture
+import android.util.Log
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.drawscope.draw
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+
+private const val TAG = "MovableElement"
+
+@Composable
+internal fun MovableElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    key: ElementKey,
+    modifier: Modifier,
+    content: @Composable MovableElementScope.() -> Unit,
+) {
+    Box(modifier.element(layoutImpl, scene, key)) {
+        // Get the Element from the map. It will always be the same and we don't want to recompose
+        // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we
+        // disable read observation during the look-up in that map.
+        val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) }
+        val movableElementScope =
+            remember(layoutImpl, element, scene) {
+                MovableElementScopeImpl(layoutImpl, element, scene)
+            }
+
+        // The [Picture] to which we save the last drawing commands of this element. This is
+        // necessary because the content of this element might not be composed in this scene, in
+        // which case we still need to draw it.
+        val picture = remember { Picture() }
+
+        // Whether we should compose the movable element here. The scene picker logic to know in
+        // which scene we should compose/draw a movable element might depend on the current
+        // transition progress, so we put this in a derivedStateOf to prevent many recompositions
+        // during the transition.
+        val shouldComposeMovableElement by
+            remember(layoutImpl, scene.key, element) {
+                derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
+            }
+
+        if (shouldComposeMovableElement) {
+            Box(
+                Modifier.drawWithCache {
+                    val width = size.width.toInt()
+                    val height = size.height.toInt()
+
+                    onDrawWithContent {
+                        // Save the draw commands into [picture] for later to draw the last content
+                        // even when this movable content is not composed.
+                        val pictureCanvas = Canvas(picture.beginRecording(width, height))
+                        draw(this, this.layoutDirection, pictureCanvas, this.size) {
+                            this@onDrawWithContent.drawContent()
+                        }
+                        picture.endRecording()
+
+                        // Draw the content.
+                        drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
+                    }
+                }
+            ) {
+                element.movableContent { movableElementScope.content() }
+            }
+        } else {
+            // If we are not composed, we draw the previous drawing commands at the same size as the
+            // movable content when it was composed in this scene.
+            val sceneValues = element.sceneValues.getValue(scene.key)
+
+            Spacer(
+                Modifier.layout { measurable, _ ->
+                        val size =
+                            sceneValues.targetSize.takeIf { it != Element.SizeUnspecified }
+                                ?: IntSize.Zero
+                        val placeable =
+                            measurable.measure(Constraints.fixed(size.width, size.height))
+                        layout(size.width, size.height) { placeable.place(0, 0) }
+                    }
+                    .drawBehind {
+                        drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
+                    }
+            )
+        }
+    }
+}
+
+private fun shouldComposeMovableElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: SceneKey,
+    element: Element,
+): Boolean {
+    val transitionState = layoutImpl.state.transitionState
+
+    // If we are idle, there is only one [scene] that is composed so we can compose our movable
+    // content here.
+    if (transitionState is TransitionState.Idle) {
+        check(transitionState.currentScene == scene)
+        return true
+    }
+
+    val fromScene = (transitionState as TransitionState.Transition).fromScene
+    val toScene = transitionState.toScene
+    if (fromScene == toScene) {
+        check(fromScene == scene)
+        return true
+    }
+
+    val fromReady = layoutImpl.isSceneReady(fromScene)
+    val toReady = layoutImpl.isSceneReady(toScene)
+
+    val otherScene =
+        when (scene) {
+            fromScene -> toScene
+            toScene -> fromScene
+            else ->
+                error(
+                    "shouldComposeMovableElement(scene=$scene) called with fromScene=$fromScene " +
+                        "and toScene=$toScene"
+                )
+        }
+
+    val isShared = otherScene in element.sceneValues
+
+    if (isShared && !toReady && !fromReady) {
+        // This should usually not happen given that fromScene should be ready, but let's log a
+        // warning here in case it does so it helps debugging flicker issues caused by this part of
+        // the code.
+        Log.w(
+            TAG,
+            "MovableElement $element might have to be composed for the first time in both " +
+                "fromScene=$fromScene and toScene=$toScene. This will probably lead to a flicker " +
+                "where the size of the element will jump from IntSize.Zero to its actual size " +
+                "during the transition."
+        )
+    }
+
+    // Element is not shared in this transition.
+    if (!isShared) {
+        return true
+    }
+
+    // toScene is not ready (because we are composing it for the first time), so we compose it there
+    // first. This is the most common scenario when starting a transition that has a shared movable
+    // element.
+    if (!toReady) {
+        return scene == toScene
+    }
+
+    // This should usually not happen, but if we are also composing for the first time in fromScene
+    // then we should compose it there only.
+    if (!fromReady) {
+        return scene == fromScene
+    }
+
+    return shouldDrawOrComposeSharedElement(
+        layoutImpl,
+        transitionState,
+        scene,
+        element.key,
+        sharedElementTransformation(layoutImpl, transitionState, element.key),
+    )
+}
+
+private class MovableElementScopeImpl(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: Element,
+    private val scene: Scene,
+) : MovableElementScope {
+    @Composable
+    override fun <T> animateSharedValueAsState(
+        value: T,
+        debugName: String,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T> {
+        val key = remember { ValueKey(debugName) }
+        return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
similarity index 87%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 1b79dbd..ccdec6e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -52,14 +52,7 @@
          * scene, this value will remain true after the pointer is no longer touching the screen and
          * will be true in any transition created to animate back to the original position.
          */
-        val isInitiatedByUserInput: Boolean,
-
-        /**
-         * Whether user input is currently driving the transition. For example, if a user is
-         * dragging a pointer, this emits true. Once they lift their finger, this emits false while
-         * the transition completes/settles.
-         */
-        val isUserInputOngoing: Flow<Boolean>,
+        val isUserInputDriven: Boolean,
     ) : ObservableTransitionState()
 }
 
@@ -80,8 +73,7 @@
                             fromScene = state.fromScene,
                             toScene = state.toScene,
                             progress = snapshotFlow { state.progress },
-                            isInitiatedByUserInput = state.isInitiatedByUserInput,
-                            isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+                            isUserInputDriven = state.isUserInputDriven,
                         )
                     }
                 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
similarity index 76%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 3985233..9c799b28 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
@@ -90,4 +91,29 @@
             canOverflow,
         )
     }
+
+    @Composable
+    override fun MovableElement(
+        key: ElementKey,
+        modifier: Modifier,
+        content: @Composable MovableElementScope.() -> Unit,
+    ) {
+        MovableElement(layoutImpl, scene, key, modifier, content)
+    }
+}
+
+/** The destination scene when swiping up or left from [upOrLeft]. */
+internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
+    return when (orientation) {
+        Orientation.Vertical -> userActions[Swipe.Up]
+        Orientation.Horizontal -> userActions[Swipe.Left]
+    }
+}
+
+/** The destination scene when swiping down or right from [downOrRight]. */
+internal fun Scene.downOrRight(orientation: Orientation): SceneKey? {
+    return when (orientation) {
+        Orientation.Vertical -> userActions[Swipe.Down]
+        Orientation.Horizontal -> userActions[Swipe.Right]
+    }
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
similarity index 76%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 58c7bdb..74e66d2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -85,6 +85,13 @@
     )
 }
 
+/**
+ * A DSL marker to prevent people from nesting calls to Modifier.element() inside a MovableElement,
+ * which is not supported.
+ */
+@DslMarker annotation class ElementDsl
+
+@ElementDsl
 interface SceneScope {
     /**
      * Tag an element identified by [key].
@@ -95,12 +102,37 @@
      * Additionally, this [key] will be used to detect elements that are shared between scenes to
      * automatically interpolate their size, offset and [shared values][animateSharedValueAsState].
      *
+     * Note that shared elements tagged using this function will be duplicated in each scene they
+     * are part of, so any **internal** state (e.g. state created using `remember {
+     * mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use
+     * [MovableElement] instead.
+     *
+     * @see MovableElement
+     *
      * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable
      *   constraint.
      */
     @Composable fun Modifier.element(key: ElementKey): Modifier
 
     /**
+     * Create a *movable* element identified by [key].
+     *
+     * This creates an element that will be automatically shared when present in multiple scenes and
+     * that can be transformed during transitions, the same way that [element] does. The major
+     * difference with [element] is that elements created with [MovableElement] will be "moved" and
+     * composed only once during transitions (as opposed to [element] that duplicates shared
+     * elements) so that any internal state is preserved during and after the transition.
+     *
+     * @see element
+     */
+    @Composable
+    fun MovableElement(
+        key: ElementKey,
+        modifier: Modifier,
+        content: @Composable MovableElementScope.() -> Unit,
+    )
+
+    /**
      * Animate some value of a shared element.
      *
      * @param value the value of this shared value in the current scene.
@@ -126,6 +158,19 @@
     ): State<T>
 }
 
+// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
+// arguments to allow sharing values inside a movable element.
+@ElementDsl
+interface MovableElementScope {
+    @Composable
+    fun <T> animateSharedValueAsState(
+        value: T,
+        debugName: String,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T>
+}
+
 /** An action performed by the user. */
 sealed interface UserAction
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
similarity index 96%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index b3a7a8e9..a40b299 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.activity.compose.BackHandler
+import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
@@ -39,7 +40,8 @@
 import com.android.compose.ui.util.fastForEach
 import kotlinx.coroutines.channels.Channel
 
-internal class SceneTransitionLayoutImpl(
+@VisibleForTesting
+class SceneTransitionLayoutImpl(
     onChangeScene: (SceneKey) -> Unit,
     builder: SceneTransitionLayoutScope.() -> Unit,
     transitions: SceneTransitions,
@@ -60,7 +62,7 @@
      * The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
      * any scene configured or right before the first measure pass of the layout.
      */
-    internal var size by mutableStateOf(IntSize.Zero)
+    @VisibleForTesting var size by mutableStateOf(IntSize.Zero)
 
     init {
         setScenes(builder)
@@ -199,4 +201,6 @@
         return readyScenes.containsKey(transition.fromScene) &&
             readyScenes.containsKey(transition.toScene)
     }
+
+    internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene)
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
similarity index 94%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index b9f83c5..7a21211 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -70,9 +70,6 @@
         val progress: Float
 
         /** Whether the transition was triggered by user input rather than being programmatic. */
-        val isInitiatedByUserInput: Boolean
-
-        /** Whether user input is currently driving the transition. */
-        val isUserInputOngoing: Boolean
+        val isUserInputDriven: Boolean
     }
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
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
new file mode 100644
index 0000000..2dc53ab
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -0,0 +1,666 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
+ */
+@Composable
+internal fun Modifier.swipeToScene(
+    layoutImpl: SceneTransitionLayoutImpl,
+    orientation: Orientation,
+): Modifier {
+    val gestureHandler = rememberSceneGestureHandler(layoutImpl, orientation)
+
+    /** Whether swipe should be enabled in the given [orientation]. */
+    fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
+        upOrLeft(orientation) != null || downOrRight(orientation) != null
+
+    val currentScene = gestureHandler.currentScene
+    val canSwipe = currentScene.shouldEnableSwipes(orientation)
+    val canOppositeSwipe =
+        currentScene.shouldEnableSwipes(
+            when (orientation) {
+                Orientation.Vertical -> Orientation.Horizontal
+                Orientation.Horizontal -> Orientation.Vertical
+            }
+        )
+
+    return nestedScroll(connection = gestureHandler.nestedScroll.connection)
+        .draggable(
+            state = rememberDraggableState(onDelta = gestureHandler.draggable::onDelta),
+            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.isAnimatingOffset &&
+                    !canOppositeSwipe,
+            onDragStarted = gestureHandler.draggable::onDragStarted,
+            onDragStopped = gestureHandler.draggable::onDragStopped,
+        )
+}
+
+@Composable
+private fun rememberSceneGestureHandler(
+    layoutImpl: SceneTransitionLayoutImpl,
+    orientation: Orientation,
+): SceneGestureHandler {
+    val coroutineScope = rememberCoroutineScope()
+
+    val gestureHandler =
+        remember(layoutImpl, orientation, coroutineScope) {
+            SceneGestureHandler(layoutImpl, orientation, coroutineScope)
+        }
+
+    // Make sure we reset the scroll connection when this handler is removed from composition
+    val connection = gestureHandler.nestedScroll.connection
+    DisposableEffect(connection) { onDispose { connection.reset() } }
+
+    return gestureHandler
+}
+
+@VisibleForTesting
+class SceneGestureHandler(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    internal val orientation: Orientation,
+    private val coroutineScope: CoroutineScope,
+) : GestureHandler {
+    override val draggable: DraggableHandler = SceneDraggableHandler(this)
+
+    override val nestedScroll: SceneNestedScrollHandler = SceneNestedScrollHandler(this)
+
+    private var transitionState
+        get() = layoutImpl.state.transitionState
+        set(value) {
+            layoutImpl.state.transitionState = value
+        }
+
+    /**
+     * The transition controlled by this gesture handler. It will be set as the [transitionState] in
+     * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition.
+     *
+     * Note: the initialScene here does not matter, it's only used for initializing the transition
+     * and will be replaced when a drag event starts.
+     */
+    private val swipeTransition = SwipeTransition(initialScene = currentScene)
+
+    internal val currentScene: Scene
+        get() = layoutImpl.scene(transitionState.currentScene)
+
+    @VisibleForTesting
+    val isDrivingTransition
+        get() = transitionState == swipeTransition
+
+    @VisibleForTesting
+    var isAnimatingOffset
+        get() = swipeTransition.isAnimatingOffset
+        private set(value) {
+            swipeTransition.isAnimatingOffset = value
+        }
+
+    internal val swipeTransitionToScene
+        get() = swipeTransition._toScene
+
+    /**
+     * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
+     * as SwipeableV2Defaults.VelocityThreshold.
+     */
+    @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
+
+    /**
+     * The positional threshold at which the intent of the user is to swipe to the next scene. It is
+     * the same as SwipeableV2Defaults.PositionalThreshold.
+     */
+    private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() }
+
+    internal var gestureWithPriority: Any? = null
+
+    internal fun onDragStarted() {
+        if (isDrivingTransition) {
+            // This [transition] was already driving the animation: simply take over it.
+            // Stop animating and start from where the current offset.
+            swipeTransition.stopOffsetAnimation()
+            return
+        }
+
+        val transition = transitionState
+        if (transition is TransitionState.Transition) {
+            // TODO(b/290184746): Better handle interruptions here if state != idle.
+            Log.w(
+                TAG,
+                "start from TransitionState.Transition is not fully supported: from" +
+                    " ${transition.fromScene} to ${transition.toScene} " +
+                    "(progress ${transition.progress})"
+            )
+        }
+
+        val fromScene = currentScene
+
+        swipeTransition._currentScene = fromScene
+        swipeTransition._fromScene = fromScene
+
+        // We don't know where we are transitioning to yet given that the drag just started, so set
+        // it to fromScene, which will effectively be treated the same as Idle(fromScene).
+        swipeTransition._toScene = fromScene
+
+        swipeTransition.stopOffsetAnimation()
+        swipeTransition.dragOffset = 0f
+
+        // 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.
+        swipeTransition.absoluteDistance =
+            when (orientation) {
+                Orientation.Horizontal -> layoutImpl.size.width
+                Orientation.Vertical -> layoutImpl.size.height
+            }.toFloat()
+
+        if (swipeTransition.absoluteDistance > 0f) {
+            transitionState = swipeTransition
+        }
+    }
+
+    internal fun onDrag(delta: Float) {
+        if (delta == 0f) return
+
+        swipeTransition.dragOffset += delta
+
+        // First check transition.fromScene should be changed for the case where the user quickly
+        // swiped twice in a row to accelerate the transition and go from A => B then B => C really
+        // fast.
+        maybeHandleAcceleratedSwipe()
+
+        val offset = swipeTransition.dragOffset
+        val fromScene = swipeTransition._fromScene
+
+        // Compute the target scene depending on the current offset.
+        val target = fromScene.findTargetSceneAndDistance(offset)
+
+        if (swipeTransition._toScene.key != target.sceneKey) {
+            swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
+        }
+
+        if (swipeTransition._distance != target.distance) {
+            swipeTransition._distance = target.distance
+        }
+    }
+
+    /**
+     * Change fromScene in the case where the user quickly swiped multiple times in the same
+     * direction to accelerate the transition from A => B then B => C.
+     */
+    private fun maybeHandleAcceleratedSwipe() {
+        val toScene = swipeTransition._toScene
+        val fromScene = swipeTransition._fromScene
+
+        // If the swipe was not committed, don't do anything.
+        if (fromScene == toScene || swipeTransition._currentScene != toScene) {
+            return
+        }
+
+        // If the offset is past the distance then let's change fromScene so that the user can swipe
+        // to the next screen or go back to the previous one.
+        val offset = swipeTransition.dragOffset
+        val absoluteDistance = swipeTransition.absoluteDistance
+        if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
+            swipeTransition.dragOffset += absoluteDistance
+            swipeTransition._fromScene = toScene
+        } else if (
+            offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key
+        ) {
+            swipeTransition.dragOffset -= absoluteDistance
+            swipeTransition._fromScene = toScene
+        }
+
+        // Important note: toScene and distance will be updated right after this function is called,
+        // using fromScene and dragOffset.
+    }
+
+    private class TargetScene(
+        val sceneKey: SceneKey,
+        val distance: Float,
+    )
+
+    private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
+        val maxDistance =
+            when (orientation) {
+                Orientation.Horizontal -> layoutImpl.size.width
+                Orientation.Vertical -> layoutImpl.size.height
+            }.toFloat()
+
+        val upOrLeft = upOrLeft(orientation)
+        val downOrRight = downOrRight(orientation)
+
+        // Compute the target scene depending on the current offset.
+        return when {
+            directionOffset < 0f && upOrLeft != null -> {
+                TargetScene(
+                    sceneKey = upOrLeft,
+                    distance = -maxDistance,
+                )
+            }
+            directionOffset > 0f && downOrRight != null -> {
+                TargetScene(
+                    sceneKey = downOrRight,
+                    distance = maxDistance,
+                )
+            }
+            else -> {
+                TargetScene(
+                    sceneKey = key,
+                    distance = 0f,
+                )
+            }
+        }
+    }
+
+    internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
+        // The state was changed since the drag started; don't do anything.
+        if (!isDrivingTransition) {
+            return
+        }
+
+        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
+            // swipeables and back handlers will be refreshed and the user can for instance quickly
+            // swipe vertically from A => B then horizontally from B => C, or swipe from A => B then
+            // immediately go back B => A.
+            if (targetScene != swipeTransition._currentScene) {
+                swipeTransition._currentScene = targetScene
+                layoutImpl.onChangeScene(targetScene.key)
+            }
+
+            animateOffset(
+                initialVelocity = velocity,
+                targetOffset = targetOffset,
+                targetScene = targetScene.key
+            )
+        }
+
+        val fromScene = swipeTransition._fromScene
+        if (canChangeScene) {
+            // If we are halfway between two scenes, we check what the target will be based on the
+            // velocity and offset of the transition, then we launch the animation.
+
+            val toScene = swipeTransition._toScene
+            if (fromScene == toScene) {
+                // We were not animating.
+                transitionState = TransitionState.Idle(fromScene.key)
+                return
+            }
+
+            // Compute the destination scene (and therefore offset) to settle in.
+            val offset = swipeTransition.dragOffset
+            val distance = swipeTransition.distance
+            if (
+                shouldCommitSwipe(
+                    offset,
+                    distance,
+                    velocity,
+                    wasCommitted = swipeTransition._currentScene == toScene,
+                )
+            ) {
+                // Animate to the next scene
+                animateTo(targetScene = toScene, targetOffset = distance)
+            } else {
+                // Animate to the initial scene
+                animateTo(targetScene = fromScene, targetOffset = 0f)
+            }
+        } else {
+            // We are doing an overscroll animation between scenes. In this case, we can also start
+            // from the idle position.
+
+            val startFromIdlePosition = swipeTransition.dragOffset == 0f
+
+            if (startFromIdlePosition) {
+                // If there is a next scene, we start the overscroll animation.
+                val target = fromScene.findTargetSceneAndDistance(velocity)
+                val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+                if (isValidTarget) {
+                    swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
+                    swipeTransition._distance = target.distance
+
+                    animateTo(targetScene = fromScene, targetOffset = 0f)
+                } else {
+                    // We will not animate
+                    transitionState = TransitionState.Idle(fromScene.key)
+                }
+            } else {
+                // We were between two scenes: animate to the initial scene.
+                animateTo(targetScene = fromScene, targetOffset = 0f)
+            }
+        }
+    }
+
+    /**
+     * Whether the swipe to the target scene should be committed or not. This is inspired by
+     * SwipeableV2.computeTarget().
+     */
+    private fun shouldCommitSwipe(
+        offset: Float,
+        distance: Float,
+        velocity: Float,
+        wasCommitted: Boolean,
+    ): Boolean {
+        fun isCloserToTarget(): Boolean {
+            return (offset - distance).absoluteValue < offset.absoluteValue
+        }
+
+        // Swiping up or left.
+        if (distance < 0f) {
+            return if (offset > 0f || velocity >= velocityThreshold) {
+                false
+            } else {
+                velocity <= -velocityThreshold ||
+                    (offset <= -positionalThreshold && !wasCommitted) ||
+                    isCloserToTarget()
+            }
+        }
+
+        // Swiping down or right.
+        return if (offset < 0f || velocity <= -velocityThreshold) {
+            false
+        } else {
+            velocity >= velocityThreshold ||
+                (offset >= positionalThreshold && !wasCommitted) ||
+                isCloserToTarget()
+        }
+    }
+
+    private fun animateOffset(
+        initialVelocity: Float,
+        targetOffset: Float,
+        targetScene: SceneKey,
+    ) {
+        swipeTransition.startOffsetAnimation {
+            coroutineScope.launch {
+                if (!isAnimatingOffset) {
+                    swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
+                }
+                isAnimatingOffset = true
+
+                swipeTransition.offsetAnimatable.animateTo(
+                    targetOffset,
+                    // TODO(b/290184746): Make this spring spec configurable.
+                    spring(
+                        stiffness = Spring.StiffnessMediumLow,
+                        visibilityThreshold = OffsetVisibilityThreshold
+                    ),
+                    initialVelocity = initialVelocity,
+                )
+
+                isAnimatingOffset = false
+
+                // Now that the animation is done, the state should be idle. Note that if the state
+                // was changed since this animation started, some external code changed it and we
+                // shouldn't do anything here. Note also that this job will be cancelled in the case
+                // where the user intercepts this swipe.
+                if (isDrivingTransition) {
+                    transitionState = TransitionState.Idle(targetScene)
+                }
+            }
+        }
+    }
+
+    private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
+        var _currentScene by mutableStateOf(initialScene)
+        override val currentScene: SceneKey
+            get() = _currentScene.key
+
+        var _fromScene by mutableStateOf(initialScene)
+        override val fromScene: SceneKey
+            get() = _fromScene.key
+
+        var _toScene by mutableStateOf(initialScene)
+        override val toScene: SceneKey
+            get() = _toScene.key
+
+        override val progress: Float
+            get() {
+                val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+                if (distance == 0f) {
+                    // This can happen only if fromScene == toScene.
+                    error(
+                        "Transition.progress should be called only when Transition.fromScene != " +
+                            "Transition.toScene"
+                    )
+                }
+                return offset / distance
+            }
+
+        override val isUserInputDriven = true
+
+        /** The current offset caused by the drag gesture. */
+        var dragOffset by mutableFloatStateOf(0f)
+
+        /**
+         * Whether the offset is animated (the user lifted their finger) or if it is driven by
+         * gesture.
+         */
+        var isAnimatingOffset by mutableStateOf(false)
+
+        /** The animatable used to animate the offset once the user lifted its finger. */
+        val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
+
+        /** Job to check that there is at most one offset animation in progress. */
+        private var offsetAnimationJob: Job? = null
+
+        /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
+        fun startOffsetAnimation(job: () -> Job) {
+            stopOffsetAnimation()
+            offsetAnimationJob = job()
+        }
+
+        /** Stops any ongoing offset animation. */
+        fun stopOffsetAnimation() {
+            offsetAnimationJob?.cancel()
+
+            if (isAnimatingOffset) {
+                isAnimatingOffset = false
+                dragOffset = offsetAnimatable.value
+            }
+        }
+
+        /** The absolute distance between [fromScene] and [toScene]. */
+        var absoluteDistance = 0f
+
+        /**
+         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
+         * above or to the left of [toScene].
+         */
+        var _distance by mutableFloatStateOf(0f)
+        val distance: Float
+            get() = _distance
+    }
+
+    companion object {
+        private const val TAG = "SceneGestureHandler"
+    }
+}
+
+private class SceneDraggableHandler(
+    private val gestureHandler: SceneGestureHandler,
+) : DraggableHandler {
+    override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) {
+        gestureHandler.gestureWithPriority = this
+        gestureHandler.onDragStarted()
+    }
+
+    override fun onDelta(pixels: Float) {
+        if (gestureHandler.gestureWithPriority == this) {
+            gestureHandler.onDrag(delta = pixels)
+        }
+    }
+
+    override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) {
+        if (gestureHandler.gestureWithPriority == this) {
+            gestureHandler.gestureWithPriority = null
+            gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
+        }
+    }
+}
+
+@VisibleForTesting
+class SceneNestedScrollHandler(
+    private val gestureHandler: SceneGestureHandler,
+) : NestedScrollHandler {
+    override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
+
+    private fun Offset.toAmount() =
+        when (gestureHandler.orientation) {
+            Orientation.Horizontal -> x
+            Orientation.Vertical -> y
+        }
+
+    private fun Velocity.toAmount() =
+        when (gestureHandler.orientation) {
+            Orientation.Horizontal -> x
+            Orientation.Vertical -> y
+        }
+
+    private fun Float.toOffset() =
+        when (gestureHandler.orientation) {
+            Orientation.Horizontal -> Offset(x = this, y = 0f)
+            Orientation.Vertical -> Offset(x = 0f, y = this)
+        }
+
+    private fun nestedScrollConnection(): PriorityNestedScrollConnection {
+        // The next potential scene is calculated during the canStart
+        var nextScene: SceneKey? = null
+
+        // This is the scene on which we will have priority during the scroll gesture.
+        var priorityScene: SceneKey? = null
+
+        // If we performed a long gesture before entering priority mode, we would have to avoid
+        // moving on to the next scene.
+        var gestureStartedOnNestedChild = false
+
+        fun findNextScene(amount: Float): SceneKey? {
+            val fromScene = gestureHandler.currentScene
+            return when {
+                amount < 0f -> fromScene.upOrLeft(gestureHandler.orientation)
+                amount > 0f -> fromScene.downOrRight(gestureHandler.orientation)
+                else -> null
+            }
+        }
+
+        return PriorityNestedScrollConnection(
+            canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+                gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+
+                val canInterceptPreScroll =
+                    gestureHandler.isDrivingTransition &&
+                        !gestureStartedOnNestedChild &&
+                        offsetAvailable.toAmount() != 0f
+
+                if (!canInterceptPreScroll) return@PriorityNestedScrollConnection false
+
+                nextScene = gestureHandler.swipeTransitionToScene.key
+
+                true
+            },
+            canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
+                val amount = offsetAvailable.toAmount()
+                if (amount == 0f) return@PriorityNestedScrollConnection false
+
+                gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+                nextScene = findNextScene(amount)
+                nextScene != null
+            },
+            canStartPostFling = { velocityAvailable ->
+                val amount = velocityAvailable.toAmount()
+                if (amount == 0f) return@PriorityNestedScrollConnection false
+
+                // We could start an overscroll animation
+                gestureStartedOnNestedChild = true
+                nextScene = findNextScene(amount)
+                nextScene != null
+            },
+            canContinueScroll = { priorityScene == gestureHandler.swipeTransitionToScene.key },
+            onStart = {
+                gestureHandler.gestureWithPriority = this
+                priorityScene = nextScene
+                gestureHandler.onDragStarted()
+            },
+            onScroll = { offsetAvailable ->
+                if (gestureHandler.gestureWithPriority != this) {
+                    return@PriorityNestedScrollConnection Offset.Zero
+                }
+
+                val amount = offsetAvailable.toAmount()
+
+                // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
+                // initiated in a nested child.
+                gestureHandler.onDrag(amount)
+
+                amount.toOffset()
+            },
+            onStop = { velocityAvailable ->
+                if (gestureHandler.gestureWithPriority != this) {
+                    return@PriorityNestedScrollConnection Velocity.Zero
+                }
+
+                priorityScene = null
+
+                gestureHandler.onDragStopped(
+                    velocity = velocityAvailable.toAmount(),
+                    canChangeScene = !gestureStartedOnNestedChild
+                )
+
+                // The onDragStopped animation consumes any remaining velocity.
+                velocityAvailable
+            },
+        )
+    }
+}
+
+/**
+ * The number of pixels below which there won't be a visible difference in the transition and from
+ * which the animation can stop.
+ */
+private const val OffsetVisibilityThreshold = 0.5f
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
similarity index 84%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 4966977..7b7ddfa 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -120,8 +120,14 @@
      *
      * @param enabled whether the matched element(s) should actually be shared in this transition.
      *   Defaults to true.
+     * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
+     *   should draw or compose this shared element.
      */
-    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+    fun sharedElement(
+        matcher: ElementMatcher,
+        enabled: Boolean = true,
+        scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
+    )
 
     /**
      * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
@@ -144,6 +150,44 @@
     fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
+interface SharedElementScenePicker {
+    /**
+     * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
+     * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
+     * [toScene].
+     */
+    fun sceneDuringTransition(
+        element: ElementKey,
+        fromScene: SceneKey,
+        toScene: SceneKey,
+        progress: () -> Float,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float,
+    ): SceneKey
+}
+
+object DefaultSharedElementScenePicker : SharedElementScenePicker {
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        fromScene: SceneKey,
+        toScene: SceneKey,
+        progress: () -> Float,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey {
+        // By default shared elements are drawn in the highest scene possible, unless it is a
+        // background.
+        return if (
+            (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
+                (fromSceneZIndex < toSceneZIndex && element.isBackground)
+        ) {
+            fromScene
+        } else {
+            toScene
+        }
+    }
+}
+
 @TransitionDsl
 interface PropertyTransformationBuilder {
     /**
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
similarity index 97%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index f1c2717..d2bfd91 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -111,8 +111,12 @@
         range = null
     }
 
-    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
-        transformations.add(SharedElementTransformation(matcher, enabled))
+    override fun sharedElement(
+        matcher: ElementMatcher,
+        enabled: Boolean,
+        scenePicker: SharedElementScenePicker,
+    ) {
+        transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
     }
 
     override fun timestampRange(
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
similarity index 97%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2ef8d56..0db8469 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -21,6 +21,7 @@
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SharedElementScenePicker
 import com.android.compose.animation.scene.TransitionState
 
 /** A transformation applied to one or more elements during a transition. */
@@ -48,6 +49,7 @@
 internal class SharedElementTransformation(
     override val matcher: ElementMatcher,
     internal val enabled: Boolean,
+    internal val scenePicker: SharedElementScenePicker,
 ) : Transformation
 
 /** A transformation that is applied on the element during the whole transition. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
similarity index 97%
rename from packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
index 27f0948..790665a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
@@ -116,9 +116,14 @@
 
         if (sizeCache.rowHeights.size != rows) {
             sizeCache.rowHeights = IntArray(rows) { 0 }
+        } else {
+            repeat(rows) { i -> sizeCache.rowHeights[i] = 0 }
         }
+
         if (sizeCache.columnWidths.size != columns) {
             sizeCache.columnWidths = IntArray(columns) { 0 }
+        } else {
+            repeat(columns) { i -> sizeCache.columnWidths[i] = 0 }
         }
 
         val totalHorizontalSpacingBetweenChildren =
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
similarity index 74%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 793a9a5..824c10b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,22 +22,23 @@
 import androidx.compose.ui.unit.Velocity
 
 /**
- * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via
- * [canStart]) if it should take over scrolling. If it does, it will scroll before its children,
- * until [canContinueScroll] allows it.
+ * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
+ * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling.
+ * If it does, it will scroll before its children, until [canContinueScroll] allows it.
  *
  * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
  * after [onStart].
  *
  * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
  */
-class PriorityPostNestedScrollConnection(
-    private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+class PriorityNestedScrollConnection(
+    private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+    private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+    private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
     private val canContinueScroll: () -> Boolean,
     private val onStart: () -> Unit,
     private val onScroll: (offsetAvailable: Offset) -> Offset,
     private val onStop: (velocityAvailable: Velocity) -> Velocity,
-    private val onPostFling: suspend (velocityAvailable: Velocity) -> Velocity,
 ) : NestedScrollConnection {
 
     /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
@@ -57,26 +58,21 @@
         if (
             isPriorityMode ||
                 source == NestedScrollSource.Fling ||
-                !canStart(available, offsetBeforeStart)
+                !canStartPostScroll(available, offsetBeforeStart)
         ) {
             // The priority mode cannot start so we won't consume the available offset.
             return Offset.Zero
         }
 
-        // Step 1: It's our turn! We start capturing scroll events when one of our children has an
-        // available offset following a scroll event.
-        isPriorityMode = true
-
-        // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
-        // lifted (step 3b), or this object has been destroyed (step 3c).
-        onStart()
-
-        return onScroll(available)
+        return onPriorityStart(available)
     }
 
     override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
         if (!isPriorityMode) {
             if (source != NestedScrollSource.Fling) {
+                if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
+                    return onPriorityStart(available)
+                }
                 // We want to track the amount of offset consumed before entering priority mode
                 offsetScrolledBeforePriorityMode += available
             }
@@ -87,6 +83,11 @@
         if (!canContinueScroll()) {
             // Step 3a: We have lost priority and we no longer need to intercept scroll events.
             onPriorityStop(velocity = Velocity.Zero)
+
+            // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+            // We want to track the amount of offset consumed before entering priority mode
+            offsetScrolledBeforePriorityMode += available
+
             return Offset.Zero
         }
 
@@ -101,7 +102,14 @@
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-        return onPostFling(available)
+        if (!canStartPostFling(available)) {
+            return Velocity.Zero
+        }
+
+        onPriorityStart(available = Offset.Zero)
+
+        // This is the last event of a scroll gesture.
+        return onPriorityStop(available)
     }
 
     /** Method to call before destroying the object or to reset the initial state. */
@@ -110,8 +118,23 @@
         onPriorityStop(velocity = Velocity.Zero)
     }
 
-    private fun onPriorityStop(velocity: Velocity): Velocity {
+    private fun onPriorityStart(available: Offset): Offset {
+        if (isPriorityMode) {
+            error("This should never happen, onPriorityStart() was called when isPriorityMode")
+        }
 
+        // Step 1: It's our turn! We start capturing scroll events when one of our children has an
+        // available offset following a scroll event.
+        isPriorityMode = true
+
+        // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
+        // lifted (step 3b), or this object has been destroyed (step 3c).
+        onStart()
+
+        return onScroll(available)
+    }
+
+    private fun onPriorityStop(velocity: Velocity): Velocity {
         // We can restart tracking the consumed offsets from scratch.
         offsetScrolledBeforePriorityMode = Offset.Zero
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
new file mode 100644
index 0000000..b53fae2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+    name: "PlatformComposeSceneTransitionLayoutTests",
+    manifest: "AndroidManifest.xml",
+    test_suites: ["device-tests"],
+    sdk_version: "current",
+    certificate: "platform",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "PlatformComposeSceneTransitionLayout",
+
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+
+        "truth",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+    use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1a9172e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.compose.animation.scene.tests" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.compose.animation.scene.tests"
+                     android:label="Tests for SceneTransitionLayout"/>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
new file mode 100644
index 0000000..7b7695e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.ui.util.lerp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnimatedSharedAsStateTest {
+    @get:Rule val rule = createComposeRule()
+
+    private data class Values(
+        val int: Int,
+        val float: Float,
+        val dp: Dp,
+        val color: Color,
+    )
+
+    private fun lerp(start: Values, stop: Values, fraction: Float): Values {
+        return Values(
+            int = lerp(start.int, stop.int, fraction),
+            float = lerp(start.float, stop.float, fraction),
+            dp = lerp(start.dp, stop.dp, fraction),
+            color = lerp(start.color, stop.color, fraction),
+        )
+    }
+
+    @Composable
+    private fun SceneScope.Foo(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val key = TestElements.Foo
+        Box(Modifier.element(key)) {
+            val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key)
+            val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key)
+            val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key)
+            val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key)
+
+            // Make sure we read the values during composition, so that we recompose and call
+            // onCurrentValueChanged() with the latest values.
+            val currentValues = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(currentValues) }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.MovableFoo(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val key = TestElements.Foo
+        MovableElement(key = key, Modifier) {
+            val int by
+                animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName)
+            val float by
+                animateSharedFloatAsState(
+                    targetValues.float,
+                    debugName = TestValues.Value2.debugName
+                )
+            val dp by
+                animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName)
+            val color by
+                animateSharedColorAsState(
+                    targetValues.color,
+                    debugName = TestValues.Value4.debugName
+                )
+
+            // Make sure we read the values during composition, so that we recompose and call
+            // onCurrentValueChanged() with the latest values.
+            val currentValues = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(currentValues) }
+        }
+    }
+
+    @Test
+    fun animateSharedValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it })
+            },
+            toSceneContent = {
+                Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use Modifier.element() here, animateSharedXAsState is composed in
+                // both scenes and values should be interpolated with the transition fraction.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(32) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(48) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.75f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(toValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+
+    @Test
+    fun movableAnimateSharedValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                MovableFoo(
+                    targetValues = fromValues,
+                    onCurrentValueChanged = { lastValueInFrom = it }
+                )
+            },
+            toSceneContent = {
+                MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use MovableElement here, animateSharedXAsState is composed only
+                // once, in the highest scene (in this case, in toScene).
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
+            }
+
+            at(32) {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
+            }
+
+            at(48) {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
new file mode 100644
index 0000000..83af630
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.assertSizeIsEqualTo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MovableElementTest {
+    @get:Rule val rule = createComposeRule()
+
+    /** An element that displays a counter that is incremented whenever this element is clicked. */
+    @Composable
+    private fun Counter(modifier: Modifier = Modifier) {
+        var count by remember { mutableIntStateOf(0) }
+        Box(modifier.fillMaxSize().clickable { count++ }) { Text("count: $count") }
+    }
+
+    @Composable
+    private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) {
+        MovableElement(key, modifier) { Counter() }
+    }
+
+    @Test
+    fun modifierElementIsDuplicatedDuringTransitions() {
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.element(TestElements.Foo).size(50.dp)) { Counter() }
+            },
+            toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } },
+            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                // Click 3 times on the counter.
+                rule.onNodeWithText("count: 0").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 1").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 2").assertIsDisplayed().performClick()
+                rule
+                    .onNodeWithText("count: 3")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(50.dp, 50.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            at(32) {
+                // In the middle of the transition, there are 2 copies of the counter: the previous
+                // one from scene A (equal to 3) and the new one from scene B (equal to 0).
+                rule
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+                    )
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(75.dp, 75.dp)
+
+                rule
+                    .onNode(
+                        hasText("count: 0") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+                    )
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(75.dp, 75.dp)
+
+                // There are exactly 2 counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(2)
+            }
+
+            after {
+                // At the end of the transition, only the counter from scene B is composed.
+                rule
+                    .onNodeWithText("count: 0")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(100.dp, 100.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+        }
+    }
+
+    @Test
+    fun movableElementIsMovedAndComposedOnlyOnce() {
+        rule.testTransition(
+            fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
+            toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
+            transition = {
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+                sharedElement(
+                    TestElements.Foo,
+                    scenePicker =
+                        object : SharedElementScenePicker {
+                            override fun sceneDuringTransition(
+                                element: ElementKey,
+                                fromScene: SceneKey,
+                                toScene: SceneKey,
+                                progress: () -> Float,
+                                fromSceneZIndex: Float,
+                                toSceneZIndex: Float
+                            ): SceneKey {
+                                assertThat(fromScene).isEqualTo(TestScenes.SceneA)
+                                assertThat(toScene).isEqualTo(TestScenes.SceneB)
+                                assertThat(fromSceneZIndex).isEqualTo(0)
+                                assertThat(toSceneZIndex).isEqualTo(1)
+
+                                // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
+                                // in Scene B.
+                                return if (progress() < 0.65f) {
+                                    TestScenes.SceneA
+                                } else {
+                                    TestScenes.SceneB
+                                }
+                            }
+                        }
+                )
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                // Click 3 times on the counter.
+                rule.onNodeWithText("count: 0").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 1").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 2").assertIsDisplayed().performClick()
+                rule
+                    .onNodeWithText("count: 3")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(50.dp, 50.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            at(32) {
+                // During the transition, there is a single counter that is moved, with the current
+                // value. Given that progress = 0.5f, it is currently composed in SceneA.
+                rule
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+                    )
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(75.dp, 75.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            at(48) {
+                // During the transition, there is a single counter that is moved, with the current
+                // value. Given that progress = 0.75f, it is currently composed in SceneB.
+                rule
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+                    )
+                    .assertIsDisplayed()
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            after {
+                // At the end of the transition, the counter still has the current value.
+                rule
+                    .onNodeWithText("count: 3")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(100.dp, 100.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
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
new file mode 100644
index 0000000..6791a85
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -0,0 +1,363 @@
+package com.android.compose.animation.scene
+
+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.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
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TransitionState.Idle
+import com.android.compose.animation.scene.TransitionState.Transition
+import com.android.compose.test.MonotonicClockTestScope
+import com.android.compose.test.runMonotonicClockTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val SCREEN_SIZE = 100f
+
+@RunWith(AndroidJUnit4::class)
+class SceneGestureHandlerTest {
+    private class TestGestureScope(
+        val coroutineScope: MonotonicClockTestScope,
+    ) {
+        private var internalCurrentScene: SceneKey by mutableStateOf(SceneA)
+
+        private val layoutState: SceneTransitionLayoutState =
+            SceneTransitionLayoutState(internalCurrentScene)
+
+        private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
+            scene(
+                key = SceneA,
+                userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC),
+            ) {
+                Text("SceneA")
+            }
+            scene(SceneB) { Text("SceneB") }
+            scene(SceneC) { Text("SceneC") }
+        }
+
+        val sceneGestureHandler =
+            SceneGestureHandler(
+                layoutImpl =
+                    SceneTransitionLayoutImpl(
+                            onChangeScene = { internalCurrentScene = it },
+                            builder = scenesBuilder,
+                            transitions = EmptyTestTransitions,
+                            state = layoutState,
+                            density = Density(1f)
+                        )
+                        .also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) },
+                orientation = Orientation.Vertical,
+                coroutineScope = coroutineScope,
+            )
+
+        val draggable = sceneGestureHandler.draggable
+
+        val nestedScroll = sceneGestureHandler.nestedScroll.connection
+
+        val velocityThreshold = sceneGestureHandler.velocityThreshold
+
+        // 10% of the screen
+        val deltaInPixels10 = SCREEN_SIZE * 0.1f
+
+        // Offset y: 10% of the screen
+        val offsetY10 = Offset(x = 0f, y = deltaInPixels10)
+
+        val transitionState: TransitionState
+            get() = layoutState.transitionState
+
+        fun advanceUntilIdle() {
+            coroutineScope.testScheduler.advanceUntilIdle()
+        }
+
+        fun runCurrent() {
+            coroutineScope.testScheduler.runCurrent()
+        }
+
+        fun assertScene(currentScene: SceneKey, isIdle: Boolean) {
+            val idleMsg = if (isIdle) "MUST" else "MUST NOT"
+            assertWithMessage("transitionState $idleMsg be Idle")
+                .that(transitionState is Idle)
+                .isEqualTo(isIdle)
+            assertThat(transitionState.currentScene).isEqualTo(currentScene)
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
+        runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
+    }
+
+    @Test
+    fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) }
+
+    @Test
+    fun onDragStarted_shouldStartATransition() = runGestureTest {
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+    }
+
+    @Test
+    fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+        val transition = transitionState as Transition
+
+        draggable.onDelta(pixels = deltaInPixels10)
+        assertThat(transition.progress).isEqualTo(0.1f)
+
+        draggable.onDelta(pixels = deltaInPixels10)
+        assertThat(transition.progress).isEqualTo(0.2f)
+    }
+
+    @Test
+    fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDelta(pixels = deltaInPixels10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDragStopped(
+            coroutineScope = coroutineScope,
+            velocity = velocityThreshold - 0.01f,
+        )
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDelta(pixels = deltaInPixels10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDragStopped(
+            coroutineScope = coroutineScope,
+            velocity = velocityThreshold,
+        )
+        assertScene(currentScene = SceneC, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneC, isIdle = true)
+    }
+
+    @Test
+    fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest {
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDragStopped(coroutineScope = coroutineScope, velocity = 0f)
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDelta(pixels = deltaInPixels10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        draggable.onDragStopped(
+            coroutineScope = coroutineScope,
+            velocity = velocityThreshold,
+        )
+
+        // The stop animation is not started yet
+        assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+
+        runCurrent()
+
+        assertThat(sceneGestureHandler.isAnimatingOffset).isTrue()
+        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+        assertScene(currentScene = SceneC, isIdle = false)
+
+        // Start a new gesture while the offset is animating
+        draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+        assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+    }
+
+    @Test
+    fun onInitialPreScroll_doNotChangeState() = runGestureTest {
+        nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun onPostScrollWithNothingAvailable_doNotChangeState() = runGestureTest {
+        val consumed =
+            nestedScroll.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset.Zero,
+                source = NestedScrollSource.Drag
+            )
+
+        assertScene(currentScene = SceneA, isIdle = true)
+        assertThat(consumed).isEqualTo(Offset.Zero)
+    }
+
+    @Test
+    fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest {
+        val consumed =
+            nestedScroll.onPostScroll(
+                consumed = Offset.Zero,
+                available = offsetY10,
+                source = NestedScrollSource.Drag
+            )
+
+        assertScene(currentScene = SceneA, isIdle = false)
+        val transition = transitionState as Transition
+        assertThat(transition.progress).isEqualTo(0.1f)
+        assertThat(consumed).isEqualTo(offsetY10)
+    }
+
+    private fun TestGestureScope.nestedScrollEvents(
+        available: Offset,
+        consumedByScroll: Offset = Offset.Zero,
+    ) {
+        val consumedByPreScroll =
+            nestedScroll.onPreScroll(available = available, source = NestedScrollSource.Drag)
+        val consumed = consumedByPreScroll + consumedByScroll
+        nestedScroll.onPostScroll(
+            consumed = consumed,
+            available = available - consumed,
+            source = NestedScrollSource.Drag
+        )
+    }
+
+    @Test
+    fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
+        nestedScrollEvents(available = offsetY10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        val transition = transitionState as Transition
+        assertThat(transition.progress).isEqualTo(0.1f)
+
+        // start intercept preScroll
+        val consumed =
+            nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+        assertThat(transition.progress).isEqualTo(0.2f)
+
+        // do nothing on postScroll
+        nestedScroll.onPostScroll(
+            consumed = consumed,
+            available = Offset.Zero,
+            source = NestedScrollSource.Drag
+        )
+        assertThat(transition.progress).isEqualTo(0.2f)
+
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.3f)
+        assertScene(currentScene = SceneA, isIdle = false)
+    }
+
+    @Test
+    fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
+        nestedScrollEvents(available = offsetY10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        nestedScroll.onPreFling(available = Velocity.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun onPreFling_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
+        nestedScrollEvents(available = offsetY10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        assertScene(currentScene = SceneC, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneC, isIdle = true)
+    }
+
+    @Test
+    fun scrollStartedInScene_doOverscrollAnimation() = runGestureTest {
+        // we started the scroll in the scene
+        nestedScrollEvents(available = offsetY10, consumedByScroll = offsetY10)
+
+        // now we can intercept the scroll events
+        nestedScrollEvents(available = offsetY10)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        // should start an overscroll animation (the gesture started in the scene)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
+        draggable.onDelta(deltaInPixels10)
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+    @Test
+    fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
+        draggable.onDragStopped(coroutineScope, velocityThreshold)
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
+        nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun startNestedScrollWhileDragging() = runGestureTest {
+        draggable.onDragStarted(coroutineScope, Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+        val transition = transitionState as Transition
+
+        draggable.onDelta(deltaInPixels10)
+        assertThat(transition.progress).isEqualTo(0.1f)
+
+        // now we can intercept the scroll events
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.2f)
+
+        // this should be ignored, we are scrolling now!
+        draggable.onDragStopped(coroutineScope, velocityThreshold)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.3f)
+
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.4f)
+
+        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        assertScene(currentScene = SceneC, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneC, isIdle = true)
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
similarity index 98%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 328866e..5afd420 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -117,7 +117,7 @@
                 .size(size)
                 .background(Color.Red)
                 .element(TestElements.Foo)
-                .testTag(TestElements.Foo.name)
+                .testTag(TestElements.Foo.debugName)
         ) {
             // Offset the single child of Foo by some animated shared offset.
             val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo)
@@ -129,7 +129,7 @@
                     }
                     .size(30.dp)
                     .background(Color.Blue)
-                    .testTag(TestElements.Bar.name)
+                    .testTag(TestElements.Bar.debugName)
             )
         }
     }
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
similarity index 95%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 53ed2b5..df3b72a 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -63,7 +63,7 @@
             { currentScene = it },
             EmptyTestTransitions,
             state = layoutState,
-            modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name),
+            modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName),
         ) {
             scene(
                 TestScenes.SceneA,
@@ -122,8 +122,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
         assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isTrue()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Release the finger. We should now be animating back to A (currentScene = SceneA) given
         // that 55dp < positional threshold.
@@ -135,8 +134,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
         assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isFalse()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Wait for the animation to finish. We should now be in scene A.
         rule.waitForIdle()
@@ -158,8 +156,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
         assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isTrue()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Release the finger. We should now be animating to C (currentScene = SceneC) given
         // that 56dp >= positional threshold.
@@ -171,8 +168,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
         assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isFalse()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Wait for the animation to finish. We should now be in scene C.
         rule.waitForIdle()
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
similarity index 95%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
index 268057f..e0ae1be 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
@@ -22,13 +22,13 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.SemanticsNodeInteractionCollection
 import androidx.compose.ui.test.hasParent
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.onAllNodesWithTag
-import androidx.compose.ui.test.onNodeWithTag
 
 @DslMarker annotation class TransitionTestDsl
 
@@ -63,6 +63,8 @@
 
 @TransitionTestDsl
 interface TransitionTestAssertionScope {
+    fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher
+
     /**
      * Assert on [element].
      *
@@ -130,15 +132,19 @@
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
+            override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher {
+                return if (scene == null) {
+                    hasTestTag(element.testTag)
+                } else {
+                    hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
+                }
+            }
+
             override fun onElement(
                 element: ElementKey,
                 scene: SceneKey?
             ): SemanticsNodeInteraction {
-                return if (scene == null) {
-                    onNodeWithTag(element.testTag)
-                } else {
-                    onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)))
-                }
+                return onNode(isElement(element, scene))
             }
 
             override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
similarity index 94%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
index 8357262..b4c393e 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -37,6 +37,9 @@
 /** Value keys that can be reused by tests. */
 object TestValues {
     val Value1 = ValueKey("Value1")
+    val Value2 = ValueKey("Value2")
+    val Value3 = ValueKey("Value3")
+    val Value4 = ValueKey("Value4")
 }
 
 // We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
similarity index 99%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2af3638..e94eff3 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -32,7 +32,6 @@
 import com.android.compose.animation.scene.TestScenes
 import com.android.compose.animation.scene.inScene
 import com.android.compose.animation.scene.testTransition
-import com.android.compose.modifiers.size
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.onEach
 import org.junit.Rule
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
similarity index 66%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 8e2b77a..122774b 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -29,20 +29,22 @@
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
-class PriorityPostNestedScrollConnectionTest {
-    private var canStart = false
+class PriorityNestedScrollConnectionTest {
+    private var canStartPreScroll = false
+    private var canStartPostScroll = false
+    private var canStartPostFling = false
     private var canContinueScroll = false
     private var isStarted = false
     private var lastScroll: Offset? = null
     private var returnOnScroll = Offset.Zero
     private var lastStop: Velocity? = null
     private var returnOnStop = Velocity.Zero
-    private var lastOnPostFling: Velocity? = null
-    private var returnOnPostFling = Velocity.Zero
 
     private val scrollConnection =
-        PriorityPostNestedScrollConnection(
-            canStart = { _, _ -> canStart },
+        PriorityNestedScrollConnection(
+            canStartPreScroll = { _, _ -> canStartPreScroll },
+            canStartPostScroll = { _, _ -> canStartPostScroll },
+            canStartPostFling = { canStartPostFling },
             canContinueScroll = { canContinueScroll },
             onStart = { isStarted = true },
             onScroll = {
@@ -53,10 +55,6 @@
                 lastStop = it
                 returnOnStop
             },
-            onPostFling = {
-                lastOnPostFling = it
-                returnOnPostFling
-            },
         )
 
     private val offset1 = Offset(1f, 1f)
@@ -64,8 +62,29 @@
     private val velocity1 = Velocity(1f, 1f)
     private val velocity2 = Velocity(2f, 2f)
 
-    private fun startPriorityMode() {
-        canStart = true
+    @Test
+    fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
+        canStartPreScroll = true
+
+        scrollConnection.onPostScroll(
+            consumed = Offset.Zero,
+            available = Offset.Zero,
+            source = NestedScrollSource.Drag
+        )
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPreFling(available = Velocity.Zero)
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    private fun startPriorityModePostScroll() {
+        canStartPostScroll = true
         scrollConnection.onPostScroll(
             consumed = Offset.Zero,
             available = Offset.Zero,
@@ -75,7 +94,7 @@
 
     @Test
     fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
-        canStart = true
+        canStartPostScroll = true
 
         scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
         assertThat(isStarted).isEqualTo(false)
@@ -86,7 +105,7 @@
         scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
         assertThat(isStarted).isEqualTo(false)
 
-        startPriorityMode()
+        startPriorityModePostScroll()
         assertThat(isStarted).isEqualTo(true)
     }
 
@@ -99,13 +118,13 @@
         )
         assertThat(isStarted).isEqualTo(false)
 
-        startPriorityMode()
+        startPriorityModePostScroll()
         assertThat(isStarted).isEqualTo(true)
     }
 
     @Test
     fun step1_onPriorityModeStarted_receiveAvailableOffset() {
-        canStart = true
+        canStartPostScroll = true
 
         scrollConnection.onPostScroll(
             consumed = offset1,
@@ -118,7 +137,7 @@
 
     @Test
     fun step2_onPriorityMode_shouldContinueIfAllowed() {
-        startPriorityMode()
+        startPriorityModePostScroll()
         canContinueScroll = true
 
         scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
@@ -132,7 +151,7 @@
 
     @Test
     fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
-        startPriorityMode()
+        startPriorityModePostScroll()
         canContinueScroll = false
 
         scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
@@ -142,7 +161,7 @@
 
     @Test
     fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
-        startPriorityMode()
+        startPriorityModePostScroll()
         canContinueScroll = true
 
         scrollConnection.onPreFling(available = Velocity.Zero)
@@ -152,7 +171,7 @@
 
     @Test
     fun step3c_onPriorityMode_shouldStopOnReset() {
-        startPriorityMode()
+        startPriorityModePostScroll()
         canContinueScroll = true
 
         scrollConnection.reset()
@@ -162,11 +181,34 @@
 
     @Test
     fun receive_onPostFling() = runTest {
+        canStartPostFling = true
+
         scrollConnection.onPostFling(
             consumed = velocity1,
             available = velocity2,
         )
 
-        assertThat(lastOnPostFling).isEqualTo(velocity2)
+        assertThat(lastStop).isEqualTo(velocity2)
+    }
+
+    @Test
+    fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
+        canStartPostFling = true
+
+        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPostScroll(
+            consumed = Offset.Zero,
+            available = Offset.Zero,
+            source = NestedScrollSource.Drag
+        )
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPreFling(available = Velocity.Zero)
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+        assertThat(isStarted).isEqualTo(true)
     }
 }
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
new file mode 100644
index 0000000..cb122dc
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -0,0 +1,27 @@
+package com.android.compose.test
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.TestMonotonicFrameClock
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+
+/**
+ * This method creates a [CoroutineScope] that can be used in animations created in a composable
+ * function.
+ *
+ * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ */
+@ExperimentalTestApi
+fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
+    // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
+    withContext(TestMonotonicFrameClock(this)) {
+        MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+    }
+}
+
+class MonotonicClockTestScope(
+    coroutineScope: CoroutineScope,
+    val testScheduler: TestCoroutineScheduler
+) : CoroutineScope by coroutineScope
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
new file mode 100755
index 0000000..5db27d8
--- /dev/null
+++ b/packages/SystemUI/flag_check.py
@@ -0,0 +1,134 @@
+#! /usr/bin/env python3
+
+import sys
+import re
+import argparse
+
+# partially copied from tools/repohooks/rh/hooks.py
+
+TEST_MSG = """Commit message is missing a "Flag:" line.  It must match one of the
+following case-sensitive regex:
+
+    %s
+
+The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
+
+As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
+flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+
+Some examples below:
+
+Flag: NONE
+Flag: NA
+Flag: LEGACY ENABLE_ONE_SEARCH DISABLED
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD
+
+Check the git history for more examples. It's a regex matched field.
+"""
+
+def main():
+    """Check the commit message for a 'Flag:' line."""
+    parser = argparse.ArgumentParser(
+        description='Check the commit message for a Flag: line.')
+    parser.add_argument('--msg',
+                        metavar='msg',
+                        type=str,
+                        nargs='?',
+                        default='HEAD',
+                        help='commit message to process.')
+    parser.add_argument(
+        '--files',
+        metavar='files',
+        nargs='?',
+        default='',
+        help=
+        'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.')
+    parser.add_argument(
+        '--project',
+        metavar='project',
+        type=str,
+        nargs='?',
+        default='',
+        help=
+        'REPO_PATH in repo upload to determine whether the check should run for this project.')
+
+    # Parse the arguments
+    args = parser.parse_args()
+    desc = args.msg
+    files = args.files
+    project = args.project
+
+    if not should_run_path(project, files):
+        return
+
+    field = 'Flag'
+    none = '(NONE|NA|N\/A)' # NONE|NA|N/A
+
+    typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG]
+
+    # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH
+    # Aconfig Flag name format = "packageName"."flagName"
+    # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
+    # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check.
+    #common_typos_disable
+    flagName = '([a-zA-z0-9_.])+'
+
+    #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
+    stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+    #common_typos_enable
+
+    readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD'
+
+    flagRegex = fr'^{field}: .*$'
+    check_flag = re.compile(flagRegex) #Flag:
+
+    # Ignore case for flag name format.
+    flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*'
+    check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
+
+    flagError = False
+    foundFlag = []
+    # Check for multiple "Flag:" lines and all lines should match this format
+    for line in desc.splitlines():
+        if check_flag.match(line):
+            if not check_flagName.match(line):
+                flagError = True
+                break
+            foundFlag.append(line)
+
+    # Throw error if
+    # 1. No "Flag:" line is found
+    # 2. "Flag:" doesn't follow right format.
+    if (not foundFlag) or (flagError):
+        error = TEST_MSG % (readableRegexMsg)
+        print(error)
+        sys.exit(1)
+
+    sys.exit(0)
+
+
+def should_run_path(path, files):
+    """Returns a boolean if this check should run with these paths.
+    If you want to check for a particular subdirectory under the path,
+    add a check here, call should_run_files and check for a specific sub dir path in should_run_files.
+    """
+    if not path:
+        return False
+    if path == 'frameworks/base':
+        return should_run_files(files)
+    # Default case, run for all other paths which calls this script.
+    return True
+
+
+def should_run_files(files):
+    """Returns a boolean if this check should run with these files."""
+    if not files:
+        return False
+    if 'packages/SystemUI' in files:
+        return True
+    return False
+
+
+if __name__ == '__main__':
+    main()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 07ffd11..1620088 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
@@ -19,8 +21,6 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -29,9 +29,9 @@
 import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -43,12 +43,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 @SmallTest
 public class SystemUIDialogTest extends SysuiTestCase {
@@ -76,12 +77,13 @@
 
         dialog.show();
         verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(),
-                intentFilterCaptor.capture(), eq(null), any());
+                intentFilterCaptor.capture(), ArgumentMatchers.eq(null), ArgumentMatchers.any());
         assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
         assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
 
         dialog.dismiss();
-        verify(mBroadcastDispatcher).unregisterReceiver(eq(broadcastReceiverCaptor.getValue()));
+        verify(mBroadcastDispatcher).unregisterReceiver(
+                ArgumentMatchers.eq(broadcastReceiverCaptor.getValue()));
     }
 
 
@@ -90,11 +92,12 @@
         final SystemUIDialog dialog = new SystemUIDialog(mContext, 0, false);
 
         dialog.show();
-        verify(mBroadcastDispatcher, never()).registerReceiver(any(), any(), eq(null), any());
+        verify(mBroadcastDispatcher, never()).registerReceiver(ArgumentMatchers.any(),
+                ArgumentMatchers.any(), ArgumentMatchers.eq(null), ArgumentMatchers.any());
         assertTrue(dialog.isShowing());
 
         dialog.dismiss();
-        verify(mBroadcastDispatcher, never()).unregisterReceiver(any());
+        verify(mBroadcastDispatcher, never()).unregisterReceiver(ArgumentMatchers.any());
         assertFalse(dialog.isShowing());
     }
 
diff --git a/packages/SystemUI/multivalentTestsForDevice b/packages/SystemUI/multivalentTestsForDevice
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTestsForDeviceless b/packages/SystemUI/multivalentTestsForDeviceless
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 9cc87fd..f0e3c99 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -57,6 +57,16 @@
             @Nullable ActivityLaunchAnimator.Controller animationController);
 
     /**
+     * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
+     * activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it
+     * will show over keyguard without first dimissing it. If it doesn't support it, calling this
+     * method is exactly the same as calling {@link #startPendingIntentDismissingKeyguard}.
+     */
+    void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
+            @Nullable Runnable intentSentUiThreadCallback,
+            @Nullable ActivityLaunchAnimator.Controller animationController);
+
+    /**
      * The intent flag can be specified in startActivity().
      */
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a5e5aaa..403c7c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -19,7 +19,6 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.view.View;
-import android.widget.ImageView;
 
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.annotations.DependsOn;
@@ -51,27 +50,21 @@
     void addDarkReceiver(DarkReceiver receiver);
 
     /**
-     * Adds a receiver to receive callbacks onDarkChanged
-     */
-    void addDarkReceiver(ImageView imageView);
-
-    /**
      * Must have been previously been added through one of the addDarkReceive methods above.
      */
     void removeDarkReceiver(DarkReceiver object);
 
     /**
-     * Must have been previously been added through one of the addDarkReceive methods above.
-     */
-    void removeDarkReceiver(ImageView object);
-
-    /**
      * Used to reapply darkness on an object, must have previously been added through
      * addDarkReceiver.
       */
     void applyDark(DarkReceiver object);
 
+    /** The default tint (applicable for dark backgrounds) is white */
     int DEFAULT_ICON_TINT = Color.WHITE;
+    /** To support an icon which wants to create contrast, the default tint is black-on-white. */
+    int DEFAULT_INVERSE_ICON_TINT = Color.BLACK;
+
     Rect sTmpRect = new Rect();
     int[] sTmpInt2 = new int[2];
 
@@ -88,8 +81,20 @@
     }
 
     /**
-     * @return true if more than half of the view area are in any of the given
-     *         areas, false otherwise
+     * @return the tint to apply to a foreground, given that the background is tinted
+     *         per {@link #getTint}
+     */
+    static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) {
+        if (isInAreas(tintAreas, view)) {
+            return inverseColor;
+        } else {
+            return DEFAULT_INVERSE_ICON_TINT;
+        }
+    }
+
+    /**
+     * @return true if more than half of the view's area is in any of the given area Rects, false
+     *         otherwise
      */
     static boolean isInAreas(Collection<Rect> areas, View view) {
         if (areas.isEmpty()) {
@@ -104,9 +109,40 @@
     }
 
     /**
-     * @return true if more than half of the view area are in area, false
+     * @return true if more than half of the viewBounds are in any of the given area Rects, false
      *         otherwise
      */
+    static boolean isInAreas(Collection<Rect> areas, Rect viewBounds) {
+        if (areas.isEmpty()) {
+            return true;
+        }
+        for (Rect area : areas) {
+            if (isInArea(area, viewBounds)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** @return true if more than half of the viewBounds are in the area Rect, false otherwise */
+    static boolean isInArea(Rect area, Rect viewBounds) {
+        if (area.isEmpty()) {
+            return true;
+        }
+        sTmpRect.set(area);
+        int left = viewBounds.left;
+        int width = viewBounds.width();
+
+        int intersectStart = Math.max(left, area.left);
+        int intersectEnd = Math.min(left + width, area.right);
+        int intersectAmount = Math.max(0, intersectEnd - intersectStart);
+
+        boolean coversFullStatusBar = area.top <= 0;
+        boolean majorityOfWidth = 2 * intersectAmount > width;
+        return majorityOfWidth && coversFullStatusBar;
+    }
+
+    /** @return true if more than half of the view's area is in the area Rect, false otherwise */
     static boolean isInArea(Rect area, View view) {
         if (area.isEmpty()) {
             return true;
@@ -129,7 +165,40 @@
      */
     @ProvidesInterface(version = DarkReceiver.VERSION)
     interface DarkReceiver {
-        int VERSION = 2;
+        int VERSION = 3;
+
+        /**
+         * @param areas list of regions on screen where the tint applies
+         * @param darkIntensity float representing the level of tint. In the range [0,1]
+         * @param tint the tint applicable as a foreground contrast to the dark regions. This value
+         *             is interpolated between a default light and dark tone, and is therefore
+         *             usable as-is, as long as the view is in one of the areas defined in
+         *             {@code areas}.
+         *
+         * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas}
+         *
+         * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+         * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+         * will be called in the same circumstances.
+         */
         void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint);
+
+        /**
+         * New version of onDarkChanged, which describes a tint plus an optional contrastTint
+         * that can be used if the tint is applied to the background of an icon.
+         *
+         * We use the 2 here to avoid the case where an existing override of onDarkChanged
+         * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get
+         * mistakenly cast to (int) and therefore trigger this method.
+         *
+         * @param areas list of areas where dark tint applies
+         * @param tint int describing the tint color to use
+         * @param contrastTint if desired, a contrasting color that can be used for a foreground
+         *
+         * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+         * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+         * will be called in the same circumstances.
+         */
+        default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {}
     }
 }
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index be1e655..445bdc2 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -2,45 +2,17 @@
 
 # Needed to ensure callback field references are kept in their respective
 # owning classes when the downstream callback registrars only store weak refs.
-# TODO(b/264686688): Handle these cases with more targeted annotations.
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  private com.android.keyguard.KeyguardUpdateMonitorCallback *;
-  private com.android.systemui.privacy.PrivacyConfig$Callback *;
-  private com.android.systemui.privacy.PrivacyItemController$Callback *;
-  private com.android.systemui.settings.UserTracker$Callback *;
-  private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
-  private com.android.systemui.util.service.Observer$Callback *;
-  private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
-}
-# Note that these rules are temporary companions to the above rules, required
-# for cases like Kotlin where fields with anonymous types use the anonymous type
-# rather than the supertype.
--if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+# Note that we restrict this to SysUISingleton classes, as other registering
+# classes should either *always* unregister or *never* register from their
+# constructor. We also keep callback class names for easier debugging.
+-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
+-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
+-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
   <1> *;
 }
--if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.settings.UserTracker$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.util.service.Observer$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
   <1> *;
 }
 
diff --git a/packages/SystemUI/res-keyguard/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
new file mode 100644
index 0000000..910a74a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:paddingMode="stack">
+    <item
+        android:id="@android:id/background"
+        android:gravity="center_vertical|fill_horizontal">
+        <shape
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:shape="rectangle">
+            <corners android:radius="30dp" />
+            <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+        </shape>
+    </item>
+    <item
+        android:id="@android:id/progress"
+        android:gravity="center_vertical|fill_horizontal">
+        <clip>
+            <shape
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:shape="rectangle">
+                <corners android:radius="30dp" />
+                <solid android:color="?androidprv:attr/textColorPrimary" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
new file mode 100644
index 0000000..183f0e5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.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 android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:layoutDirection="ltr"
+    android:gravity="center"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <ProgressBar
+        android:id="@+id/side_fps_progress_bar"
+        android:layout_width="55dp"
+        android:layout_height="10dp"
+        android:indeterminateOnly="false"
+        android:min="0"
+        android:max="100"
+        android:progressDrawable="@drawable/progress_bar" />
+</LinearLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index a1e2dc3..a8017f6 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -52,15 +52,22 @@
                 android:visibility="gone"
                 />
         </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_type"
-            android:layout_height="@dimen/status_bar_mobile_type_size"
+        <FrameLayout
+            android:id="@+id/mobile_type_container"
+            android:layout_height="@dimen/status_bar_mobile_container_height"
             android:layout_width="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:adjustViewBounds="true"
-            android:paddingStart="2.5sp"
-            android:paddingEnd="1sp"
-            android:visibility="gone" />
+            android:layout_marginStart="2.5sp"
+            android:layout_marginEnd="1sp"
+            android:visibility="gone"
+            >
+            <ImageView
+                android:id="@+id/mobile_type"
+                android:layout_height="@dimen/status_bar_mobile_type_size"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:adjustViewBounds="true"
+                />
+        </FrameLayout>
         <Space
             android:id="@+id/mobile_roaming_space"
             android:layout_height="match_parent"
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index 2eb1bb5..a6a8122 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Druk Kieslys om te ontsluit."</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_missing_sim_instructions" msgid="7735360104844653246">"Voeg ’n SIM by."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Die SIM is weg of nie leesbaar nie. Voeg ’n SIM by."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Onbruikbare SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Jou SIM is permanent gedeaktiveer.\n Kontak jou draadlose diensverskaffer vir ’n ander SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is gesluit."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-gesluit."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ontsluit tans SIM …"</string>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index 5fd946b..fb84414 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"ለመክፈት ምናሌ ተጫን።"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"አውታረ መረብ ተቆልፏል"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ምንም SIM የለም"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ሲም ያክሉ።"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ሲሙ ጠፍቷል ወይም አይነበብም። ሲም ያክሉ።"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ጥቅም ላይ የማይውል ሲም።"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ሲምዎ በቋሚነት ቦዝኗል።\n ለሌላ ሲም የእርስዎን አገልግሎት ሰጪ ያግኙ።"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ሲም ተቆልፏል።"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ሲም በPUK የተቆለፈ ነው።"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ሲምን በመክፈት ላይ…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index b6479f4..fb33092 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"اضغط على \"القائمة\" لإلغاء التأمين."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"الشبكة مؤمّنة"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‏لا تتوفر شريحة SIM."</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"‏يجب إضافة شريحة SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"‏شريحة SIM مفقودة أو غير قابلة للقراءة. يجب إضافة شريحة SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‏شريحة SIM غير قابلة للاستخدام."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"‏تم إيقاف شريحة SIM نهائيًا.\n عليك التواصل مع مقدم خدمة اللاسلكي للحصول على شريحة SIM أخرى."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"‏شريحة SIM مُقفَلة."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"‏شريحة SIM مُقفَلة برمز PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"‏جارٍ إلغاء قفل شريحة SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index a41a704..a123bb7 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"আনলক কৰিবলৈ মেনু টিপক।"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"নেটৱর্ক লক কৰা অৱস্থাত আছে"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"কোনো ছিম নাই"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"এখন ছিম যোগ দিয়ক।"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ছিম নাই অথবা সেইখন পঢ়িব নোৱাৰি। এখন ছিম যোগ দিয়ক।"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ব্যৱহাৰ কৰিব নোৱৰা ছিম।"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"আপোনাৰ ছিমখন স্থায়ীভাৱে নিষ্ক্ৰিয় কৰা হৈছে।\n অন্য এখন ছিমৰ বাবে আপোনাৰ ৱায়াৰলেছ সেৱা প্ৰদানকাৰীৰ সৈতে যোগাযোগ কৰক।"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ছিমখন লক হৈ আছে।"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ছিমখন PUKৰ দ্বাৰা লক হৈ আছে।"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ছিম আনলক কৰি থকা হৈছে…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index ed969c7..b133b30a 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Kilidi açmaq üçün Menyu düyməsinə bası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_missing_sim_instructions" msgid="7735360104844653246">"SIM əlavə edin."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM kart yoxdur və ya oxuna bilinmir. SIM əlavə edin."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"İstifadəyə yararsız SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM kartınız həmişəlik deaktiv edilib.\n Başqa SIM kart üçün simsiz xidmət provayderinizə müraciət edin."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM kilidlənib."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM kart PUK ilə kilidlənib."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM kiliddən çıxarılır…"</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 b0a6471..9a91962 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite Meni da biste otključali."</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_missing_sim_instructions" msgid="7735360104844653246">"Dodajte SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM nedostaje ili ne može da se pročita. Dodajte SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Neupotrebljiv SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM je trajno deaktiviran.\n Obratite se dobavljaču usluge bežične telefonije da biste dobili drugi SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM je zaključan."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM je zaključan PUK-om."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Otključava se SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 11cc77d..5e46b715 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Націсніце кнопку \"Меню\", каб разблакіраваць."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сетка заблакіравана"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Няма SIM-карты"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Дадайце SIM-карту."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-карта адсутнічае ці не чытаецца. Дадайце SIM-карту."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Непрыдатная для выкарыстання SIM-карта."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Ваша SIM-карта адключана назаўсёды.\n Звяжыцеся з аператарам бесправадной сувязі, каб атрымаць іншую SIM-карту."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-карта заблакіравана."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-карта заблакіравана PUK-кодам."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Разблакіраванне SIM-карты…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index c554a27..ab931ed 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Натиснете „Меню“, за да отключите."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежата е заключена"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Няма SIM карта"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Добавете SIM карта."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM картата липсва или е нечетлива. Добавете SIM карта."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Неизползваема SIM карта."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM картата ви е деактивирана за постоянно.\nЗа да получите друга, се свържете с доставчика си на безжична услуга."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM картата е заключена."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM картата е заключена с PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM картата се отключва…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 69f533c..e25de93 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"আনলক করতে মেনুতে টিপুন।"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"নেটওয়ার্ক লক করা আছে"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"কোনও সিম নেই"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"সিম যোগ করুন।"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"সিম নেই অথবা সেটি রিড করা যাচ্ছে না। সিম যোগ করুন।"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ব্যবহারযোগ্য নয় এমন সিম।"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"আপনার সিম স্থায়ীভাবে বন্ধ করে দেওয়া হয়েছে।\n অন্য একটি সিমের জন্য আপনার ওয়্যারলেস পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"সিম লক করা হয়েছে।"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"সিম PUK লক করা হয়েছে।"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"সিম আনলক করা হচ্ছে…"</string>
@@ -125,7 +121,7 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"চালিয়ে যেতে আপনার ডিভাইস আনলক করুন"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"পরে ইনস্টল আপডেট করতে পিন লিখুন"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"পরে আপডেট ইনস্টল করতে পিন লিখুন"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"পরে আপডেট ইনস্টল করতে পাসওয়ার্ড লিখুন"</string>
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"পরে আপডেট ইনস্টল করতে প্যাটার্ন আঁকুন"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"ডিভাইস আপডেট করা হয়েছে। চালিয়ে যেতে পিন লিখুন।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index 4c519c8..cd7aaeb 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite meni da otključate."</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_missing_sim_instructions" msgid="7735360104844653246">"Dodajte SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM nedostaje ili se ne može čitati. Dodajte SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Neupotrebljiv SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM je trajno deaktiviran.\n Kontaktirajte pružaoca bežičnih usluga za drugi SIM"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM je zaključan."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM je zaključan PUK-om."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Otključavanje SIM-a…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 3bd6508..bf8a592 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Prem Menú per desbloquejar."</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_missing_sim_instructions" msgid="7735360104844653246">"Afegeix una SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Falta la SIM o no es pot llegir. Afegeix una SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"La SIM no es pot utilitzar."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"La SIM s\'ha desactivat permanentment.\n Contacta amb el proveïdor de serveis sense fil per obtenir-ne una altra."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La SIM està bloquejada."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La SIM està bloquejada pel PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"S\'està desbloquejant la targeta SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index e075d85..bedafd8 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Klávesy odemknete stisknutím tlačítka nabídky."</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_missing_sim_instructions" msgid="7735360104844653246">"Přidejte SIM kartu."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM karta chybí nebo je nečitelná. Přidejte SIM kartu."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM kartu nelze použít."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM karta byla natrvalo deaktivována.\n Požádejte svého poskytovatele bezdrátových služeb o další SIM kartu."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karta je zablokována."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karta je blokována pomocí kódu PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Odblokování SIM karty…"</string>
@@ -125,9 +121,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Pokud chcete pokračovat, odemkněte zařízení"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Pokud aktualizaci chcete nainstalovat později, zadejte PIN"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Pokud aktualizaci chcete nainstalovat později, zadejte heslo"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Pokud aktualizaci chcete nainstalovat později, zadejte gesto"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Zadejte PIN a aktualizaci nainstalujte později"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Zadejte heslo a aktualizaci nainstalujte později"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Zadejte gesto a aktualizaci nainstalujte později"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte PIN."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte heslo."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte gesto."</string>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index c7c863b..93f505e 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Tryk på menuen for at låse op."</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_missing_sim_instructions" msgid="7735360104844653246">"Tilføj et SIM-kort."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kortet mangler eller kan ikke læses. Tilføj et SIM-kort."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Deaktiveret SIM-kort."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Dit SIM-kort er permanent deaktiveret.\n Kontakt din tjenesteudbyder for at få et nyt SIM-kort."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortet er låst."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortet er låst med PUK-koden."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-kortet låses op…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 117f7a9..01e166e 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Zum Entsperren die Menütaste drücken."</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_missing_sim_instructions" msgid="7735360104844653246">"Lege eine SIM-Karte ein."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-Karte fehlt oder ist nicht lesbar. Lege eine SIM-Karte ein."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-Karte ist nicht nutzbar."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Deine SIM-Karte wurde dauerhaft deaktiviert.\n Wende dich an deinen Mobilfunkanbieter, um eine andere SIM-Karte zu erhalten."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-Karte ist gesperrt."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-Karte ist mit einem PUK gesperrt."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-Karte wird entsperrt…"</string>
@@ -125,9 +121,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Gerät entsperren, um fortzufahren"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Gib deine PIN ein, um das Update später zu installieren"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Gib dein Passwort ein, um das Update später zu installieren"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Zeichne dein Muster, um das Update später zu installieren"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"PIN eingeben, um Update später zu installieren"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Passwort eingeben, um Update später zu installieren"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Muster zeichnen, um Update später zu installieren"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Gerät aktualisiert. Gib deine PIN ein, um fortzufahren."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Gerät aktualisiert. Gib dein Passwort ein, um fortzufahren."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Gerät aktualisiert. Zeichne dein Muster, um fortzufahren."</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index cd7637c..9769242 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Πατήστε \"Μενού\" για ξεκλείδωμα."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Κλειδωμένο δίκτυο"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Δεν υπάρχει SIM"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Προσθέστε μια SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Η SIM λείπει ή δεν είναι δυνατή η ανάγνωσή της. Προσθέστε μια SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Η SIM δεν μπορεί να χρησιμοποιηθεί."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Η SIM απενεργοποιήθηκε οριστικά.\n Επικοινωνήστε με τον πάροχο υπηρεσιών ασύρματου δικτύου για μια νέα SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Η SIM είναι κλειδωμένη."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Η SIM έχει κλειδωθεί με κωδικό PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ξεκλείδωμα SIM…"</string>
@@ -125,9 +121,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ξεκλειδώστε τη συσκευή σας για να συνεχίσετε"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Εισαγάγετε το PIN για να εγκαταστήσετε την ενημέρωση αργότερα"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Εισαγωγή PIN για εγκατάσταση ενημέρωσης αργότερα"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Εισαγ. τον κωδ. πρόσβασης για να εγκαταστήσετε την ενημέρωση αργότερα"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Σχεδιάστε το μοτίβο για να εγκαταστήσετε την ενημέρωση αργότερα"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Σχεδιάστε το μοτίβο για εγκατάσταση της ενημέρωσης αργότερα"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Η συσκευή ενημερώθηκε. Εισαγάγετε το PIN για να συνεχίσετε."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Η συσκευή ενημερώθηκε. Εισαγάγ. τον κωδ. πρόσβασης για να συνεχίσετε."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Η συσκευή ενημερώθηκε. Σχεδιάστε το μοτίβο για να συνεχίσετε."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 0ace8a7..087ab3a 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</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_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string>
@@ -130,5 +126,5 @@
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 480bcbb..7297cf9 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</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_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking 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 0ace8a7..087ab3a 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</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_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string>
@@ -130,5 +126,5 @@
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 0ace8a7..087ab3a 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</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_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string>
@@ -130,5 +126,5 @@
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index b8e89f4..ead8bce 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎Press Menu to unlock.‎‏‎‎‏‎"</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_missing_sim_instructions" msgid="7735360104844653246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎Add a SIM.‎‏‎‎‏‎"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎The SIM is missing or not readable. Add a SIM.‎‏‎‎‏‎"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎Unusable SIM.‎‏‎‎‏‎"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎Your SIM has been permanently deactivated.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ Contact your wireless service provider for another SIM.‎‏‎‎‏‎"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎SIM is locked.‎‏‎‎‏‎"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎SIM is PUK-locked.‎‏‎‎‏‎"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎Unlocking 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 debbeb1..5b82c44 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Presiona Menú para desbloquear."</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_missing_sim_instructions" msgid="7735360104844653246">"Introduce una tarjeta SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Falta la tarjeta SIM o no se puede leer. Introduce una tarjeta SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Tarjeta SIM inutilizable."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Tu tarjeta SIM se desactivó permanentemente.\n Ponte en contacto con tu proveedor de servicios inalámbricos para obtener otra tarjeta SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La tarjeta SIM está bloqueada."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La tarjeta SIM está bloqueada con el código PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando tarjeta SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index 0ea98a8..cf7f3d2 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pulsa el menú para desbloquear la pantalla."</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_missing_sim_instructions" msgid="7735360104844653246">"Añade una SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Falta la SIM o no se puede leer. Añade una SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"No se puede usar la SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Tu SIM se ha desactivado de forma permanente.\n Para obtener otra SIM, ponte en contacto con tu proveedor de servicios inalámbricos."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La SIM está bloqueada."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La SIM está bloqueada con el código PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 722a022..6335ca8 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Vajutage avamiseks menüüklahvi."</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_missing_sim_instructions" msgid="7735360104844653246">"Lisage SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM puudub või pole loetav. Lisage SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-i ei saa kasutada."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Teie SIM on jäädavalt inaktiveeritud.\n Teise SIM-i saamiseks võtke ühendust oma traadita side teenusepakkujaga."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM on lukustatud."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM on PUK-koodiga lukustatud."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-i avamine …"</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index d329369..b47c58a 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Desblokeatzeko, sakatu Menua."</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_missing_sim_instructions" msgid="7735360104844653246">"Gehitu SIM bat."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIMa falta da, edo ezin da irakurri. Gehitu SIM bat."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ezin da erabili SIMa."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Betiko desaktibatu da SIMa.\n Jarri operadorearekin harremanetan beste SIM bat eskuratzeko."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIMa blokeatuta dago."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIMa PUKaren bidez desblokeatu behar da."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIMa desblokeatzen…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 4815815..f274f5f 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"برای باز کردن قفل روی «منو» فشار دهید."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"شبکه قفل شد"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"سیم‌کارتی وجود ندارد"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"سیم‌کارت اضافه کنید."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"سیم‌کارت موجود نیست یا قابل‌خواندن نیست. سیم‌کارت اضافه کنید."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"سیم‌کارت قابل‌استفاده نیست."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"سیم‌کارت شما برای همیشه غیرفعال شده است.\n برای دریافت سیم‌کارتی دیگر، با رساننده خدمات بی‌سیم خود تماس بگیرید."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"سیم‌کارت قفل است."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"‏سیم‌کارت با کد PUK قفل شده است."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"درحال باز کردن قفل سیم‌کارت…"</string>
@@ -125,9 +121,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"برای ادامه، قفل دستگاهتان را باز کنید"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"برای نصب به‌روزرسانی در فرصتی دیگر، پین را وارد کنید"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"برای نصب به‌روزرسانی در فرصتی دیگر، گذرواژه را وارد کنید"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"برای نصب به‌روزرسانی در فرصتی دیگر، الگو را وارد کنید"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"پین را وارد کنید و به‌روزرسانی را در فرصتی دیگر انجام دهید"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"گذرواژه را وارد کنید و به‌روزرسانی را در فرصتی دیگر انجام دهید"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"الگو را وارد کنید و به‌روزرسانی را در فرصتی دیگر انجام دهید"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"دستگاه به‌روز شد. برای ادامه، پین را وارد کنید."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"دستگاه به‌روز شد. برای ادامه، گذرواژه را وارد کنید."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"دستگاه به‌روز شد. برای ادامه، الگو را وارد کنید."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index 02d41d8..dd9ce2e4 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Poista lukitus painamalla Valikkoa."</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_missing_sim_instructions" msgid="7735360104844653246">"Lisää SIM-kortti."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-korttia ei löydy tai ei voi lukea. Lisää SIM-kortti."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-korttia ei voi käyttää."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Sim-kortti on poistettu käytöstä pysyvästi.\n Ota yhteyttä langattoman palvelun tarjoajaan ja pyydä uusi SIM-kortti."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortti on lukittu."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortti on lukittu PUK-koodilla."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-kortin lukitusta avataan…"</string>
@@ -127,7 +123,7 @@
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jatka avaamalla laitteen lukitus"</string>
     <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Jos haluat asentaa päivityksen myöhemmin, lisää PIN-koodi"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Jos haluat asentaa päivityksen myöhemmin, lisää salasana"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Jos haluat asentaa päivityksen myöhemmin, piirrä kuvio"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Salli päivitys myöhemmin piirtämällä kuvio"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Laite päivitetty. Jatka lisäämällä PIN-koodi."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Laite päivitetty. Jatka lisäämällä salasana."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Laite päivitetty. Jatka piirtämällä kuvio."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index fa1a191..742f56e 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Appuyez sur la touche Menu pour déverrouiller l\'appareil."</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_missing_sim_instructions" msgid="7735360104844653246">"Ajouter une carte SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"La carte SIM est manquante ou illisible. Ajouter une carte SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"La carte SIM est inutilisable."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Votre carte SIM a été désactivée de manière permanente.\n Communiquez avec votre fournisseur de services sans fil pour obtenir une autre carte SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La carte SIM est verrouillée."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La carte SIM est verrouillée par clé PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Déverrouillage de la carte SIM en cours…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index d687a1d..92d24c4 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Appuyez sur \"Menu\" pour déverrouiller le clavier."</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_missing_sim_instructions" msgid="7735360104844653246">"Ajoutez une SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"La SIM est absente ou illisible. Ajoutez une SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilisable."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Votre SIM a été désactivée définitivement.\n Contactez votre opérateur de téléphonie mobile pour en obtenir une autre."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM verrouillée."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM verrouillée par clé PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Déblocage de la SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 3faa7ca..4837de2 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Preme Menú para desbloquear."</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_missing_sim_instructions" msgid="7735360104844653246">"Engade unha SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"A SIM falta ou non se pode ler. Engade unha."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"A SIM non se pode usar."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"A SIM desactivouse permanentemente.\n Ponte en contacto co teu fornecedor de servizos sen fíos para conseguir outra."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"A SIM está bloqueada."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"A SIM está bloqueada mediante PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando SIM…"</string>
@@ -130,5 +126,5 @@
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Debuxa o padrón para instalar a actualización máis tarde"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Actualizouse o dispositivo. Mete o PIN para continuar."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Actualizouse o dispositivo. Mete o contrasinal para continuar."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón para continuar."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 99c9883..7f8c6d8 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"અનલૉક કરવા માટે મેનૂ દબાવો."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"નેટવર્ક લૉક થયું"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"કોઈ સિમ કાર્ડ નથી"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"સિમ કાર્ડ ઉમેરો."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"સિમ કાર્ડ ખૂટે છે અથવા વાંચી શકાય એવું નથી. સિમ કાર્ડ ઉમેરો."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ઉપયોગમાં ન લઈ શકાતું સિમ કાર્ડ."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"તમારું સિમ કાર્ડ કાયમ માટે નિષ્ક્રિય કરવામાં આવ્યું છે.\n બીજા સિમ કાર્ડ માટે તમારા વાયરલેસ સેવા પ્રદાતાનો સંપર્ક કરો."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"સિમ કાર્ડ લૉક કરેલું છે."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"સિમ કાર્ડ PUK-લૉક કરેલું છે."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"સિમ કાર્ડ અનલૉક કરી રહ્યાં છીએ…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 9d32f04..18d63ab 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"लॉक खोलने के लिए मेन्यू दबाएं."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लॉक किया हुआ है"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"कोई सिम नहीं है"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"कोई सिम जोड़ें."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"सिम मौजूद नहीं है या उसे ऐक्सेस नहीं किया जा सकता. कोई सिम जोड़ें."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"सिम को हमेशा के लिए बंद कर दिया गया है."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"आपका सिम हमेशा के लिए बंद कर दिया गया है.\n दूसरा सिम पाने के लिए, वायरलेस सेवा देने वाली कंपनी से संपर्क करें."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"सिम लॉक है."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"सिम में PUK लॉक लगा है."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"सिम अनलॉक हो रहा है…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index b4224bf..0206faf 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite Izbornik da biste otključali."</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_missing_sim_instructions" msgid="7735360104844653246">"Dodajte SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM nedostaje ili nije čitljiv. Dodajte SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM je neupotrebljiv."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Vaš je SIM trajno deaktiviran.\n Obratite se svom davatelju bežičnih usluga da biste dobili drugi SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM je zaključan."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM je zaključan PUK-om."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Otključavanje SIM-a…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index bc712c7..8575e10 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"A feloldáshoz nyomja meg a Menü gombot."</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_missing_sim_instructions" msgid="7735360104844653246">"Adjon hozzá egy SIM-et."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"A SIM hiányzik vagy nem olvasható. Adjon hozzá egy SIM-et."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nem használható SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM véglegesen deaktiválva.\n Forduljon a vezeték nélküli szolgáltatójához másik SIM beszerzése érdekében."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Zárolt SIM."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"A SIM le van zárva PUK-kóddal."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM zárolásának feloldása…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 4d7bbbe..a7c3aba 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ապակողպելու համար սեղմեք Ընտրացանկը:"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Ցանցը կողպված է"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM քարտ չկա"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Ավելացրեք SIM քարտ։"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM քարտը բացակայում է կամ ընթեռնելի չէ։ Ավելացրեք SIM քարտ։"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Անվավեր SIM քարտ։"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Ձեր SIM քարտն ընդմիշտ ապակտիվացվել է։\n Նոր SIM քարտ ձեռք բերելու համար կապվեք ձեր բջջային օպերատորի հետ։"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM քարտը կողպված է։"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM քարտը կողպված է PUK կոդով։"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM քարտն ապակողպվում է…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index aa766e9..f9a840f 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Tekan Menu untuk membuka kunci."</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_missing_sim_instructions" msgid="7735360104844653246">"Tambahkan SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM tidak ada atau tidak dapat dibaca. Tambahkan SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM tidak dapat digunakan."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM Anda telah dinonaktifkan secara permanen.\n Hubungi penyedia layanan nirkabel Anda untuk mendapatkan SIM lain."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM dikunci."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM dikunci PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Membuka kunci SIM …"</string>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 99f1779..b7147c2 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ýttu á valmyndarhnappinn til að taka úr lás."</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_missing_sim_instructions" msgid="7735360104844653246">"Bæta við SIM-korti."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kort vantar eða er ekki læsilegt. Bæta við SIM-korti."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ónothæft SIM-kort."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-kortið þitt var gert varanlega óvirkt.\n Hafðu samband við símafyrirtækið þitt til að fá nýtt SIM-kort."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kort er læst."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kort er læst með PUK-númeri."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Opnar SIM-kort…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index cc0a164..9e1b187 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Premi Menu per sbloccare."</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_missing_sim_instructions" msgid="7735360104844653246">"Aggiungi una SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM mancante o non leggibile. Aggiungi una SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilizzabile."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"La SIM è stata disattivata definitivamente.\n Contatta il tuo fornitore di servizi wireless per richiedere un\'altra SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La SIM è bloccata."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La SIM è bloccata tramite PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Sblocco della SIM in corso…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index bc66355..16316ce 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"יש ללחוץ על \'תפריט\' כדי לבטל את הנעילה."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"הרשת נעולה"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‏אין כרטיס SIM"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"‏הוספת כרטיס SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"‏כרטיס ה-SIM חסר או שלא ניתן לקרוא אותו. הוספת כרטיס SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‏לא ניתן להשתמש בכרטיס ה-SIM הזה."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"‏כרטיס ה-SIM שלך הושבת באופן סופי.\n עליך לפנות לספק השירות האלחוטי שלך לקבלת כרטיס SIM אחר."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"‏כרטיס ה-SIM נעול."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"‏כרטיס ה-SIM נעול באמצעות PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"‏מתבצע ביטול נעילה של כרטיס ה-SIM…"</string>
@@ -130,5 +126,5 @@
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"צריך למתוח את קו ביטול הנעילה כדי להתקין את העדכון מאוחר יותר"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"המכשיר עודכן. צריך להזין את קוד האימות כדי להמשיך."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"המכשיר עודכן. צריך להזין את הסיסמה כדי להמשיך."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. צריך למתוח את קו ביטול הנעילה כדי להמשיך."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. יש למתוח קו ביטול נעילה כדי להמשיך"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 1d59a63..6e8f423 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"メニューからロックを解除できます。"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ネットワークがロックされました"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM がありません"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM を追加してください。"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM が見つからないか読み取れません。SIM を追加してください。"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM が使用できません。"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM が完全に無効になっています。\n ワイヤレス サービス プロバイダにお問い合わせのうえ、新しい SIM を入手してください。"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM がロックされています。"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM が PUK でロックされました。"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM ロックを解除しています…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index 5bd6b2e..a31243d 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"განსაბლოკად დააჭირეთ მენიუს."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ქსელი ჩაკეტილია"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM არ არის"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM-ის დამატება."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM აკლია ან არ იკითხება. SIM-ის დამატება."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"გამოუყენებელი SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"თქვენი SIM სამუდამოდ გამორთულია.\n დაუკავშირდით თქვენს უკაბელო სერვისის პროვაიდერს სხვა SIM ბარათისთვის."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-ბარათი ჩაკეტილია."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM დაბლოკილია PUK-ით."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-ის განბლოკვა…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index 83d270d..6a77783 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ашу үшін \"Мәзір\" пернесін басыңыз."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Желі құлыптаулы"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM картасы жоқ."</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM картасын қосыңыз."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM картасы жоқ немесе оқылмай тұр. SIM картасын қосыңыз."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM картасын пайдалану мүмкін емес."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM картаңыз біржола өшірілді.\n Сымсыз байланыс провайдеріне хабарласып, басқа SIM картасын алыңыз."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM картасы құлыпталған."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM картасы PUK кодымен құлыпталды."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM картасының құлпы ашылып жатыр…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 5306cb1..cda9520 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"ចុចម៉ឺនុយ ​ដើម្បី​ដោះ​សោ។"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"បណ្ដាញ​ជាប់​សោ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"គ្មានស៊ីមទេ"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"បញ្ចូល​ស៊ីម។"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"បាត់ស៊ីម ឬមិនអាចអានស៊ីមបាន។ បញ្ចូល​ស៊ីម។"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ស៊ីមមិនអាចប្រើបាន។"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ស៊ីមរបស់អ្នកត្រូវបានបិទដំណើរការជាអចិន្ត្រៃយ៍។\n ទាក់ទងទៅក្រុមហ៊ុនផ្ដល់សេវាឥតខ្សែរបស់អ្នក ដើម្បីទទួលបានស៊ីមមួយទៀត។"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ស៊ីម​ត្រូវបាន​ចាក់សោ។"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ស៊ីមត្រូវបានចាក់សោដោយ PUK។"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"កំពុងដោះសោស៊ីម…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index d609a23..e24005a 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಮೆನು ಒತ್ತಿರಿ."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ನೆಟ್‌ವರ್ಕ್ ಲಾಕ್ ಆಗಿದೆ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM ಇಲ್ಲ"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM ಅನ್ನು ಸೇರಿಸಿ."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM ಕಾಣೆಯಾಗಿದೆ ಅಥವಾ ರೀಡ್ ಆಗುತ್ತಿಲ್ಲ. SIM ಅನ್ನು ಸೇರಿಸಿ."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM ನಿಷ್ಪ್ರಯೋಜಕವಾಗಿದೆ."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ನಿಮ್ಮ SIM ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ.\n ಬೇರೊಂದು SIM ಗಾಗಿ ನಿಮ್ಮ ವೈರ್‌ಲೆಸ್ ಸೇವಾ ಪೂರೈಕೆದಾರರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM ಲಾಕ್ ಆಗಿದೆ."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM PUK ಲಾಕ್ ಆಗಿದೆ."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM ಅನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 532253e..7378cc78 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"잠금 해제하려면 메뉴를 누르세요."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"네트워크 잠김"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM 없음"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM을 추가하세요."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM이 없거나 SIM을 읽을 수 없습니다. SIM을 추가하세요."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM을 사용할 수 없습니다."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM이 영구적으로 비활성화되었습니다.\n 다른 SIM을 사용하려면 무선 서비스 제공업체에 문의하시기 바랍니다."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM이 잠김 상태입니다."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM이 PUK 잠김 상태입니다."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM 잠금 해제 중…"</string>
@@ -129,6 +125,6 @@
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"나중에 업데이트를 설치하려면 비밀번호를 입력하세요."</string>
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"나중에 업데이트를 설치하려면 패턴을 그리세요."</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"기기가 업데이트되었습니다. 계속하려면 PIN을 입력하세요."</string>
-    <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속 진행하려면 비밀번호를 입력하세요."</string>
+    <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속하려면 비밀번호를 입력하세요."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"기기가 업데이트되었습니다. 계속하려면 패턴을 그리세요."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 1e03c03..88f0b97 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Кулпуну ачуу үчүн Менюну басыңыз."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Тармак кулпуланган"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM карта жок"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM карта кошуңуз."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM карта жок же окулбайт. SIM карта кошуңуз."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Жараксыз SIM карта."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM картаңыз биротоло өчүрүлдү.\n Башка SIM карта алуу үчүн зымсыз кызмат көрсөтүүчүгө кайрылыңыз."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM карта кулпуланган."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM карта PUK менен кулпуланган."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM картанын кулпусу ачылууда…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 0059d7f..00a382a 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"ກົດ \"ເມນູ\" ເພື່ອປົດລັອກ."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ເຄືອຂ່າຍຖືກລັອກ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ບໍ່ມີຊິມ"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ເພີ່ມຊິມ."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ບໍ່ມີຊິມ ຫຼື ອ່ານຊິມບໍ່ໄດ້. ເພີ່ມຊິມ."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ຊິມໃຊ້ບໍ່ໄດ້."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ຊິມຂອງທ່ານຖືກປິດໃຊ້ຢ່າງຖາວອນແລ້ວ.\n ຕິດຕໍ່ຜູ້ໃຫ້ບໍລິການໂທລະສັບໄຮ້ສາຍຂອງທ່ານເພື່ອຂໍຊິມໃໝ່."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ຊິມຖືກລັອກຢູ່."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ຊິມຖືກລັອກດ້ວຍ PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ກຳລັງປົດລັອກຊິມ…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 01e2f88..31c4107 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Paspauskite meniu, jei norite atrakinti."</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_missing_sim_instructions" msgid="7735360104844653246">"Įdėkite SIM kortelę."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Trūksta SIM kortelės arba ji neskaitoma. Įdėkite SIM kortelę."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nenaudojama SIM kortelė."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Jūsų SIM kortelė visam laikui išjungta.\n Susisiekite su belaidžio ryšio paslaugos teikėju, kad gautumėte naują SIM kortelę."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM kortelė užrakinta."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM kortelė užrakinta PUK kodu."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Atrakinama SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index 2133694..ecf2233 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Lai atbloķētu, nospiediet izvēlnes ikonu."</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_missing_sim_instructions" msgid="7735360104844653246">"Pievienojiet SIM karti."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Nav SIM kartes, vai arī to nevar nolasīt. Pievienojiet SIM karti."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM karte nav izmantojama."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Jūsu SIM karte ir neatgriezeniski deaktivizēta.\n Sazinieties ar savu bezvadu pakalpojumu sniedzēju, lai iegūtu citu SIM karti."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karte ir bloķēta."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karte ir bloķēta ar PUK kodu."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Notiek SIM kartes atbloķēšana…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index 2771c7f..3f089b9 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Притиснете „Мени“ за отклучување."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежата е заклучена"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Нема SIM-картичка"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Додајте SIM-картичка."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Нема SIM-картичка или не може да се прочита. Додајте SIM-картичка."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-картичката е неупотреблива."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Вашата SIM-картичка е трајно деактивирана.\n Контактирајте со давателот на услуги за безжична мрежа за друга SIM-картичка."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-картичката е заклучена."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-картичката е заклучена со PUK-код."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Се отклучува SIM-картичката…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 02ee66f..be1ea89 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"അൺലോക്കുചെയ്യാൻ മെനു അമർത്തുക."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"നെറ്റ്‌വർക്ക് ലോക്കുചെയ്‌തു"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"സിം ഇല്ല"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"സിം ചേർക്കുക."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"സിം കാണുന്നില്ല അല്ലെങ്കിൽ റീഡ് ചെയ്യാനായില്ല. സിം ചേർക്കുക."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ഉപയോഗശൂന്യമായ സിം."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"നിങ്ങളുടെ സിം ശാശ്വതമായി നിഷ്ക്രിയമാക്കി.\n മറ്റൊരു സിമ്മിന് നിങ്ങളുടെ വയർലെസ് സേവന ദാതാവിനെ ബന്ധപ്പെടുക."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"സിം ലോക്ക് ചെയ്തു."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"സിം PUK ലോക്ക് ചെയ്തു."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"സിം അൺലോക്ക് ചെയ്യുന്നു…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 2b9f81e..54fdecd 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Түгжээг тайлах бол цэсийг дарна уу."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сүлжээ түгжигдсэн"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM байхгүй"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM нэмнэ үү."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM дутуу эсвэл үүнийг унших боломжгүй байна. SIM нэмнэ үү."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ашиглах боломжгүй SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Таны SIM-г бүрмөсөн идэвхгүй болгосон байна.\n Өөр SIM авах бол утасгүй үйлчилгээ үзүүлэгчтэйгээ холбогдоно уу."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-г түгжсэн байна."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-г PUK-р түгжсэн байна."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-н түгжээг тайлж байна…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 7aa7bdd..eff4c7a 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"अनलॉक करण्यासाठी मेनू प्रेस करा."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लॉक केले"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"सिम नाही"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"सिम जोडा."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"सिम गहाळ झाले आहे किंवा ते रीड करू शकत नाही. सिम जोडा."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"वापरण्यायोग्य नसलेले सिम."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"तुमचे सिम कायमचे डीॲक्टिव्हेट केले गेले आहे.\n दुसऱ्या सिमसाठी तुमच्या वायरलेस सेवा पुरवठादाराशी संपर्क साधा."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"सिम लॉक केलेले आहे."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"सिम PUK लॉक केलेले आहे."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"सिम अनलॉक करत आहे…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index bdfa4a7..d9eb4ca 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Tekan Menu untuk membuka kunci."</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_missing_sim_instructions" msgid="7735360104844653246">"Tambah SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM tiada atau tidak boleh dibaca. Tambah SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM tidak boleh digunakan."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM anda telah dinyahaktifkan secara kekal.\n Hubungi penyedia perkhidmatan wayarles anda untuk mendapatkan SIM lain."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM dikunci."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM dikunci PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Membuka kunci SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index e85cf8a..afbce26 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"မီနူးကို နှိပ်၍ လော့ခ်ဖွင့်ပါ။"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ကွန်ရက်ကို လော့ခ်ချထားသည်"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ဆင်းမ်ကတ် မရှိပါ"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ဆင်းမ်ကတ်ထည့်ပါ။"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ဆင်းမ်မရှိပါ (သို့) သုံး၍မရပါ။ ဆင်းမ်ကတ်ထည့်ပါ။"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ဆင်းမ်ကတ်ကို သုံး၍မရပါ။"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"သင်၏ဆင်းမ်ကတ်ကို အပြီးပိတ်လိုက်သည်။\n ဆင်းမ်ကတ်နောက်တစ်ခု ရယူရန် သင်၏ ကြိုးမဲ့ဝန်ဆောင်မှုပေးသူထံ ဆက်သွယ်ပါ။"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ဆင်းမ်ကတ်ကို လော့ခ်ချထားသည်။"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ဆင်းမ်ကတ်၏ ပင်နံပါတ်ပြန်ဖွင့်သည့် ကုဒ်ကို လော့ခ်ချထားသည်။"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ဆင်းမ်ကတ် ဖွင့်နေသည်…"</string>
@@ -128,7 +124,7 @@
     <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပင်နံပါတ်ထည့်ပါ"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် စကားဝှက်ထည့်ပါ"</string>
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပုံဖော်ရေးဆွဲပါ"</string>
-    <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
-    <string name="kg_prompt_after_update_password" msgid="153703052501352094">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
+    <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
+    <string name="kg_prompt_after_update_password" msgid="153703052501352094">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 455d086..3098e87 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Trykk på menyknappen for å låse opp."</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_missing_sim_instructions" msgid="7735360104844653246">"Legg til et SIM-kort."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kortet mangler eller kan ikke leses. Legg til et SIM-kort."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-kortet kan ikke brukes."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-kortet er deaktivert permanent.\n Kontakt leverandøren av trådløstjenesten for å få et nytt SIM-kort."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortet er låst."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortet er låst med PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Låser opp SIM-kortet …"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index f0094a3..45b8819 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"अनलक गर्न मेनु थिच्नुहोस्।"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लक भएको छ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM कार्ड हालिएको छैन"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM कार्ड हाल्नुहोस्।"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM कार्ड हालिएको छैन वा रिड गर्न मिल्दैन। SIM कार्ड हाल्नुहोस्।"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"यो SIM कार्ड प्रयोग गर्न मिल्दैन।"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"तपाईंको SIM कार्ड सदाका लागि डिएक्टिभेट गरिएको छ।\n आफ्नो वायरलेस सेवा प्रदायकलाई सम्पर्क गरी अर्को SIM कार्ड प्राप्त गर्नुहोस्।"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM कार्ड लक गरिएको छ।"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM कार्ड PUK प्रयोग गरी लक गरिएको छ।"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM कार्ड अनलक गरिँदै छ…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index a236639..af24d40 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Druk op Menu om te ontgrendelen."</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_missing_sim_instructions" msgid="7735360104844653246">"Voeg een simkaart toe."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"De simkaart ontbreekt of kan niet worden gelezen. Voeg een simkaart toe."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Onbruikbare simkaart."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Je simkaart is permanent gedeactiveerd.\n Neem contact op met je mobiele serviceprovider voor een nieuwe simkaart."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Simkaart is vergrendeld."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Simkaart is vergrendeld met pukcode."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Simkaart ontgrendelen…"</string>
@@ -130,5 +126,5 @@
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Teken het patroon om de update later te installeren"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Apparaat geüpdatet. Voer de pincode in om door te gaan."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Apparaat geüpdatet. Voer het wachtwoord in om door te gaan."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken het patroon om door te gaan."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken patroon om door te gaan."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index b31c9c0..8cae987 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"ଅନଲକ୍‌ କରିବା ପାଇଁ ମେନୁକୁ ଦବାନ୍ତୁ।"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ନେଟୱର୍କକୁ ଲକ୍‌ କରାଯାଇଛି"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"କୌଣସି SIM ନାହିଁ"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ଏକ SIM ଯୋଗ କରନ୍ତୁ।"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM ଉପଲବ୍ଧ ନାହିଁ କିମ୍ବା ପଢ଼ିପାରିବା ଯୋଗ୍ୟ ନୁହେଁ। ଏକ SIM ଯୋଗ କରନ୍ତୁ।"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ବ୍ୟବହାର ଅଯୋଗ୍ୟ ଥିବା SIM।"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ଆପଣଙ୍କ SIMକୁ ସ୍ଥାୟୀ ଭାବରେ ନିଷ୍କ୍ରିୟ କରାଯାଇଛି।\n ଅନ୍ୟ ଏକ SIM ପାଇଁ ଆପଣଙ୍କ ୱେୟାରଲେସ ସେବା ପ୍ରଦାନକାରୀଙ୍କ ସହ କଣ୍ଟାକ୍ଟ କରନ୍ତୁ।"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIMକୁ ଲକ କରାଯାଇଛି।"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIMକୁ PUK-ଲକ କରାଯାଇଛି।"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIMକୁ ଅନଲକ କରାଯାଉଛି…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 209b63f..18959c8 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"ਅਣਲਾਕ ਕਰਨ ਲਈ \"ਮੀਨੂ\" ਦਬਾਓ।"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ਨੈੱਟਵਰਕ  ਲਾਕ  ਕੀਤਾ ਗਿਆ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ਕੋਈ ਸਿਮ ਨਹੀਂ ਹੈ"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ਸਿਮ ਸ਼ਾਮਲ ਕਰੋ।"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ਸਿਮ ਮੌਜੂਦ ਨਹੀਂ ਹੈ ਜਾਂ ਪੜ੍ਹਨਯੋਗ ਨਹੀਂ ਹੈ। ਸਿਮ ਸ਼ਾਮਲ ਕਰੋ।"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ਬੇਕਾਰ ਸਿਮ।"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ਤੁਹਾਡੇ ਸਿਮ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਅਕਿਰਿਆਸ਼ੀਲ ਕੀਤਾ ਗਿਆ ਹੈ।\n ਦੂਜੇ ਸਿਮ ਲਈ ਆਪਣੇ ਵਾਇਰਲੈੱਸ ਸੇਵਾ ਪ੍ਰਦਾਨਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ਸਿਮ ਲਾਕ ਹੈ।"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ਸਿਮ PUK-ਲਾਕ ਹੈ।"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ਸਿਮ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 7ec988e..bd00ba9 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Naciśnij Menu, aby odblokować."</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_missing_sim_instructions" msgid="7735360104844653246">"Dodaj kartę SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Brak karty SIM lub nie można jej odczytać. Dodaj kartę SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nie można użyć karty SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Karta SIM została trwale wyłączona.\n Skontaktuj się z dostawcą usług bezprzewodowych, aby uzyskać inną kartę SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Karta SIM jest zablokowana."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Karta SIM została zablokowana kodem PUK"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Odblokowuję kartę 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 78a8091..54e270f 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pressione Menu para desbloquear."</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_missing_sim_instructions" msgid="7735360104844653246">"Adicione um chip."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"O chip não foi inserido ou não pode ser lido. Adicione um chip."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Chip inutilizável."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Seu chip foi desativado permanentemente.\n Entre em contato com seu provedor de serviços sem fio para receber outro chip."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"O chip está bloqueado."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"O chip está bloqueado pela PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando chip…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index 5549b36..2e37bde 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Prima Menu para desbloquear."</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_missing_sim_instructions" msgid="7735360104844653246">"Adicione um SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"O SIM está em falta ou não é legível. Adicione um SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilizável."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"O SIM foi desativado permanentemente.\n Contacte o seu fornecedor de serviços de rede sem fios para obter outro SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"O SIM está bloqueado."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"O SIM está bloqueado com o PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"A desbloquear SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index 78a8091..54e270f 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pressione Menu para desbloquear."</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_missing_sim_instructions" msgid="7735360104844653246">"Adicione um chip."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"O chip não foi inserido ou não pode ser lido. Adicione um chip."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Chip inutilizável."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Seu chip foi desativado permanentemente.\n Entre em contato com seu provedor de serviços sem fio para receber outro chip."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"O chip está bloqueado."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"O chip está bloqueado pela PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando chip…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index e5be788..ead09209 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Apasă pe Meniu pentru a debloca."</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_missing_sim_instructions" msgid="7735360104844653246">"Adaugă un card SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Cardul SIM lipsește sau nu poate fi citit. Adaugă un card SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Cardul SIM nu se poate folosi."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Cardul tău SIM a fost dezactivat definitiv.\n Contactează furnizorul de servicii wireless pentru a obține un alt card SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Cardul SIM este blocat."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Cardul SIM este blocat prin cod PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Se deblochează cardul SIM…"</string>
@@ -127,8 +123,8 @@
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Deblochează dispozitivul pentru a continua"</string>
     <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Introdu codul PIN pentru a instala actualizarea mai târziu"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Introdu parola pentru a instala actualizarea mai târziu"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează modelul pentru a instala actualizarea mai târziu"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează pentru a instala actualizarea mai târziu"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Dispozitivul s-a actualizat. Introdu codul PIN pentru a continua."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Dispozitivul s-a actualizat. Introdu parola pentru a continua."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitivul s-a actualizat. Desenează modelul pentru a continua."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitiv actualizat. Desenează modelul și continuă."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 45149a5..595fba5 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Для разблокировки нажмите \"Меню\"."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сеть заблокирована"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM-карта отсутствует"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Добавьте SIM-карту."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-карта отсутствует или не распознана. Добавьте SIM-карту."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-карту невозможно использовать."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-карта была окончательно деактивирована.\n Чтобы получить новую, обратитесь к своему оператору мобильной связи."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-карта заблокирована."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-карта заблокирована с помощью PUK-кода."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Разблокировка SIM-карты…"</string>
@@ -128,7 +124,7 @@
     <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Введите PIN-код, чтобы установить обновление позже."</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Введите пароль, чтобы установить обновление позже."</string>
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Введите графический ключ, чтобы установить обновление позже."</string>
-    <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Введите PIN-код."</string>
-    <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Введите пароль."</string>
-    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Введите графический ключ."</string>
+    <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Чтобы продолжить, введите PIN-код."</string>
+    <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Чтобы продолжить, введите пароль."</string>
+    <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Чтобы продолжить, введите графический ключ."</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 17ced75..b6a7422 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"අගුලු හැරීමට මෙනුව ඔබන්න."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ජාලය අගුළු දමා ඇත"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM නැත"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM එකක් එක් කරන්න."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM අස්ථානගතයි හෝ කියවිය නොහැක. SIM එකක් එක් කරන්න."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"භාවිත කළ නොහැකි SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ඔබේ SIM ස්ථිරවම අක්‍රිය කර ඇත.\n වෙනත් SIM පතක් සඳහා ඔබේ රැහැන් රහිත සේවා සපයන්නා අමතන්න."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM අගුළු දමා ඇත."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM PUK-අගුළු දමා ඇත."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM අගුළු අරිමින්…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index ef08a6c..5e34a94 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Odomknete stlačením tlačidla ponuky."</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_missing_sim_instructions" msgid="7735360104844653246">"Pridajte SIM kartu."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM karta chýba alebo sa nedá čítať. Pridajte SIM kartu."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nepoužiteľná SIM karta."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Vaša SIM karta bola natrvalo deaktivovaná.\n Požiadajte svojho poskytovateľa bezdrôtových služieb o ďalšiu SIM kartu."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karta je uzamknutá."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karta je uzamknutá kódom PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM karta sa odomyká…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index a42989c..3508f3b 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Če želite odkleniti, pritisnite meni."</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_missing_sim_instructions" msgid="7735360104844653246">"Dodajte kartico SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Ni kartice SIM ali je ni mogoče prebrati. Dodajte kartico SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kartica SIM je neuporabna."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Vaša kartica SIM je bila trajno deaktivirana.\n Za drugo kartico SIM se obrnite na ponudnika brezžičnih storitev."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Kartica SIM je zaklenjena."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Kartica SIM je zaklenjena s kodo PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Odklepanje kartice SIM …"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index ce53b7e..8d71b0f 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Shtyp \"Meny\" për të shkyçur."</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_missing_sim_instructions" msgid="7735360104844653246">"Shto një kartë SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Karta SIM mungon ose është e palexueshme. Shto një kartë SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kartë SIM e papërdorshme."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Karta jote SIM është çaktivizuar përgjithmonë.\n Kontakto me ofruesin e shërbimit wireless për një tjetër kartë SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Karta SIM është e kyçur."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Karta SIM është e kyçur me PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Karta SIM po shkyçet…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index 437018d..4093952 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Притисните Мени да бисте откључали."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежа је закључана"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Нема SIM-а"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Додајте SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM недостаје или не може да се прочита. Додајте SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Неупотребљив SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM је трајно деактивиран.\n Обратите се добављачу услуге бежичне телефоније да бисте добили други SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM је закључан."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM је закључан PUK-ом."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Откључава се SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index b4b1996..5b01f39 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Lås upp genom att trycka på Meny."</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_missing_sim_instructions" msgid="7735360104844653246">"Lägg till ett SIM-kort."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kort saknas eller går inte att läsa. Lägg till ett SIM-kort."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-kortet går inte att använda."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Ditt SIM-kort har inaktiverats permanent.\n Kontakta din operatör och be om ett nytt SIM-kort."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortet är låst."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortet har låsts med PUK-kod."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-kortet låses upp …"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 8ca9046..72f1fc3 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Bonyeza Menyu ili kufungua."</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_missing_sim_instructions" msgid="7735360104844653246">"Weka SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM haipo au haiwezi kusomwa. Weka SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM haiwezi kutumika."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM kadi yako imefungwa kabisa.\n wasiliana na mtoa huduma wako wa pasi waya ili upate SIM nyingine."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM imefungwa."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM imefungwa kwa PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Inafungua SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 7671194..20eb8ef 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"அன்லாக் செய்ய மெனுவை அழுத்தவும்."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"நெட்வொர்க் பூட்டப்பட்டது"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"சிம் இல்லை"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"சிம்மைச் சேருங்கள்."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"சிம் இல்லை அல்லது படிக்கக்கூடியதாக இல்லை. சிம்மைச் சேருங்கள்."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"பயன்படுத்த முடியாத சிம்."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"உங்கள் சிம் நிரந்தரமாக முடக்கப்பட்டுள்ளது.\n மற்றொரு சிம்மிற்கான உங்கள் வயர்லெஸ் சேவை வழங்குநரைத் தொடர்புகொள்ளுங்கள்."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"சிம் லாக் செய்யப்பட்டுள்ளது."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"சிம் PUK-லாக் செய்யப்பட்டுள்ளது."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"சிம்மை அன்லாக் செய்கிறது…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index 623b589..d496944 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"అన్‌లాక్ చేయడానికి మెనూను నొక్కండి."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"నెట్‌వర్క్ లాక్ చేయబడింది"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM లేదు"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIMను జోడించండి."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM మిస్ అయ్యింది లేదా ఆమోదయోగ్యం కాదు. SIMను జోడించండి."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"వినియోగించలేని SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"మీ SIM శాశ్వతంగా డీయాక్టివేట్ చేయబడింది.\n మరో SIMను పొందడం కోసం మీ వైర్‌లెస్ సర్వీస్ ప్రొవైడర్‌ను సంప్రదించండి."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM లాక్ చేయబడింది."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM PUK లాక్ చేయబడింది."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIMను అన్‌లాక్ చేస్తోంది…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index c244107..605d077 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"กด \"เมนู\" เพื่อปลดล็อก"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"เครือข่ายถูกล็อก"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ไม่มี SIM"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"โปรดใส่ SIM"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ไม่มี SIM หรืออ่านไม่ได้ โปรดใส่ SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM ใช้งานไม่ได้"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ปิดใช้งาน SIM อย่างถาวรแล้ว\n ติดต่อผู้ให้บริการไร้สายของคุณเพื่อรับ SIM ใหม่"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM ถูกล็อก"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM ถูกล็อกด้วย PUK"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"กำลังปลดล็อก SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index cd8f810..040ec9e 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pindutin ang Menu upang i-unlock."</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_missing_sim_instructions" msgid="7735360104844653246">"Magdagdag ng SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Wala o hindi nababasa ang SIM. Magdagdag ng SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Hindi magagamit na SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Permanenteng na-deactivate ang iyong SIM.\n Makipag-ugnayan sa iyong service provider ng wireless para sa isa pang SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Naka-lock ang SIM."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Naka-PUK lock ang SIM."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ina-unlock ang SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index ddeba67..750ba11 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Kilidi açmak için Menü\'ye basın."</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_missing_sim_instructions" msgid="7735360104844653246">"SIM ekleyin."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM yok veya okunamıyor. SIM ekleyin."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kullanılamayan SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM\'iniz kalıcı olarak devre dışı bırakıldı.\n Başka bir SIM için kablosuz servis sağlayıcınızla iletişime geçin."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM kilitli."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM\'in PUK kilidi devrede."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM\'in kilidi açılıyor…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index f06d17d..169ea1f 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Натисніть меню, щоб розблокувати."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мережу заблоковано"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Немає SIM-карти"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Додайте SIM-карту."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-карта відсутня або недоступна для читання. Додайте SIM-карту."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Непридатна SIM-карта."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-карту деактивовано назавжди.\n Щоб отримати іншу, зверніться до свого постачальника послуг бездротового зв’язку."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-карту заблоковано."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-карту заблоковано PUK-кодом."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Розблокування SIM-карти…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 8adbaca..d7f7b65 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"غیر مقفل کرنے کیلئے مینیو دبائیں۔"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"نیٹ ورک مقفل ہو گیا"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‏کوئی SIM نہیں ہے"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"‏ایک SIM شامل کریں۔"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"‏SIM غائب ہے یا پڑھنے لائق نہیں ہے۔ ایک SIM شامل کریں۔"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‏ناقابل استعمال SIM۔"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"‏آپ کے SIM کو مستقل طور پر غیر فعال کر دیا گیا ہے۔\n کسی دوسرے SIM کیلئے اپنے وائرلیس سروس فراہم کنندہ سے رابطہ کریں۔"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"‏SIM مقفل ہے۔"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"‏آپ کا SIM ‏PUK مقفل ہے۔"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"‏SIM کو غیر مقفل کیا جا رہا ہے…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index 96dfa05..40dbaf3 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Qulfdan chiqarish uchun Menyu tugmasini bosing."</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_missing_sim_instructions" msgid="7735360104844653246">"SIM karta qoʻshish."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM karta topilmadi yoki oʻqilmadi. SIM karta qoʻshish."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ishlamaydigan SIM."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM karta butunlay faolsizlantirildi.\n Boshqa SIM karta olish uchun simsiz aloqa operatoriga murojaat qiling."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karta qulflandi."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karta PUK kod bilan qulflangan."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM karta qulfdan chiqarilmoqda…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 41b5a33..d5a33d3 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Nhấn vào Menu để mở khóa."</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_missing_sim_instructions" msgid="7735360104844653246">"Hãy thêm SIM."</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Không tìm thấy hoặc không đọc được SIM. Hãy thêm SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM không sử dụng được."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM của bạn đã bị vô hiệu hoá vĩnh viễn.\n Hãy liên hệ với nhà cung cấp dịch vụ viễn thông không dây của bạn để yêu cầu cấp SIM khác."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM này đang bị khoá."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM này đang bị khoá PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Đang mở khoá SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 59261a3..6de9ff9 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"按“菜单”即可解锁。"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"网络已锁定"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"没有 SIM 卡"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"请插入 SIM 卡。"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM 卡缺失或无法读取。请插入 SIM 卡。"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡无法使用。"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"您的 SIM 卡已被永久停用。\n请与您的无线服务提供商联系,以便重新获取一张 SIM 卡。"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM 卡已被锁定。"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM 卡已用 PUK 码锁定。"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"正在解锁 SIM 卡…"</string>
@@ -125,9 +121,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解锁设备才能继续操作"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"需要输入 PIN 码才能稍后安装更新"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"需要输入密码才能稍后安装更新"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"需要绘制解锁图案才能稍后安装更新"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"请输入 PIN 码,系统稍后会安装更新"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"请输入密码,系统稍后会安装更新"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"请绘制解锁图案,系统稍后会安装更新"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"设备已更新。您需要输入 PIN 码才能继续。"</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"设备已更新。您需要输入密码才能继续。"</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"设备已更新。您需要绘制解锁图案才能继续。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index dad6f31..11966ca 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"按下 [選單] 即可解鎖。"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"網絡已鎖定"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"沒有 SIM 卡"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"請新增 SIM 卡。"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"找不到 SIM 卡或 SIM 卡無法讀取,請新增 SIM 卡。"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡無法使用。"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM 卡已永久停用。\n請向無線服務供應商索取其他 SIM 卡。"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM 卡已鎖定。"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM 卡已使用 PUK 鎖定。"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"正在解鎖 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 88b7e43..e4f868a 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"按選單鍵解鎖。"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"網路已鎖定"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"沒有 SIM 卡"</string>
-    <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"請新增 SIM 卡。"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"找不到 SIM 卡或 SIM 卡無法讀取,請新增 SIM 卡。"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡無法使用。"</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM 卡已永久停用。\n 請向無線服務供應商索取其他 SIM 卡。"</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM 卡已鎖定。"</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM 卡已使用 PUK 碼鎖定。"</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"正在解鎖 SIM 卡…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index c5e99ab..4fadc2e 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -35,13 +35,9 @@
     <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_instructions_when_pattern_disabled" msgid="8448804180089936954">"Chofoza Menyu ukuvula."</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_missing_sim_instructions" msgid="7735360104844653246">"engeza i-SIM"</string>
-    <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"I-SIM ayitholakali noma ayifundeki. engeza i-SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"I-SIM engasebenziseki."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"I-SIM yakho iyekiswe ukusebenza unomphela.\n Xhumana nomhlinzeki wakho wesevisi ngokungenazintambo ukuze uthole enye i-SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"I-SIM ikhiyiwe."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"I-SIM ikhiyiwe nge-PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ivula i-SIM…"</string>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 28b5870..565ed10 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -67,23 +67,13 @@
     <!-- 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>
 
-    <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock.  This is shown in small font at the bottom. -->
-    <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
-
     <!-- SIM messages --><skip />
     <!-- When the user inserts a sim card from an unsupported network, it becomes network locked -->
     <string name="keyguard_network_locked_message">Network locked</string>
     <!-- Shown when there is no SIM. -->
     <string name="keyguard_missing_sim_message_short">No SIM</string>
-    <!-- Shown to ask the user to add a SIM. -->
-    <string name="keyguard_missing_sim_instructions">Add a SIM.</string>
-    <!-- Shown to ask the user to add a SIM when sim is missing or not readable. -->
-    <string name="keyguard_missing_sim_instructions_long">The SIM is missing or not readable. Add a SIM.</string>
     <!-- Shown when SIM is permanently disabled. -->
     <string name="keyguard_permanent_disabled_sim_message_short">Unusable SIM.</string>
-    <!-- Shown to inform the user to SIM is permanently deactivated. -->
-    <string name="keyguard_permanent_disabled_sim_instructions">Your SIM has been permanently deactivated.\n
-    Contact your wireless service provider for another SIM.</string>
     <!-- Shown to tell the user that their SIM is locked and they must unlock it. -->
     <string name="keyguard_sim_locked_message">SIM is locked.</string>
     <!-- When the user enters a wrong sim pin too many times, it becomes PUK locked (Pin Unlock Kode) -->
diff --git a/packages/SystemUI/res/color/qs_tile_ripple_color.xml b/packages/SystemUI/res/color/qs_tile_ripple_color.xml
new file mode 100644
index 0000000..c106254
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_tile_ripple_color.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:attr/colorControlHighlight" android:state_hovered="true"
+        android:state_pressed="true" />
+    <!-- RippleDrawable has default way of handling hover state with highlighting but it's not
+    consistent with our approach so we make highlighting invisible and instead do custom handling
+    of hover state on a different level -->
+    <item android:color="@color/transparent" android:state_hovered="true" />
+    <item android:color="?android:attr/colorControlHighlight" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background.xml b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
new file mode 100644
index 0000000..db25c89
--- /dev/null
+++ b/packages/SystemUI/res/drawable/mobile_network_type_background.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.
+  ~
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle"
+    >
+    <corners
+        android:topLeftRadius="0dp"
+        android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius"
+        android:bottomRightRadius="0dp"
+        android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius"/>
+    <solid android:color="#FFF" />
+    <padding
+        android:left="2sp"
+        android:right="2sp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml
index 265f575..ef3c61b 100644
--- a/packages/SystemUI/res/drawable/qs_tile_background.xml
+++ b/packages/SystemUI/res/drawable/qs_tile_background.xml
@@ -15,9 +15,25 @@
   ~ limitations under the License.
   -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="?android:attr/colorControlHighlight">
+    android:color="@color/qs_tile_ripple_color">
     <item android:id="@android:id/mask"
-        android:drawable="@drawable/qs_tile_background_shape" />
-    <item android:id="@id/background"
-        android:drawable="@drawable/qs_tile_background_shape"/>
+        android:drawable="@drawable/qs_tile_background_shape"
+        />
+    <item android:id="@id/background">
+        <layer-list>
+            <item
+                android:id="@+id/qs_tile_background_base"
+                android:drawable="@drawable/qs_tile_background_shape" />
+            <item android:id="@+id/qs_tile_background_overlay">
+                <selector>
+                    <item
+                        android:state_hovered="true"
+                        android:drawable="@drawable/qs_tile_background_shape" />
+                    <item
+                        android:state_focused="true"
+                        android:drawable="@drawable/qs_tile_background_shape" />
+                </selector>
+            </item>
+        </layer-list>
+    </item>
 </ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 1c7e997..6d77943 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -27,7 +27,6 @@
 
     <ImageView
         android:id="@+id/bluetooth_device_icon"
-        android:contentDescription="@string/accessibility_bluetooth_device_icon"
         android:layout_width="24dp"
         android:layout_height="24dp"
         app:layout_constraintStart_toStartOf="parent"
@@ -39,8 +38,12 @@
         android:layout_width="0dp"
         android:id="@+id/bluetooth_device_name"
         style="@style/BluetoothTileDialog.DeviceName"
+        android:textDirection="locale"
+        android:textAlignment="gravity"
         android:paddingStart="20dp"
         android:paddingTop="10dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         app:layout_constraintWidth_percent="0.7"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
@@ -55,6 +58,8 @@
         style="@style/BluetoothTileDialog.DeviceSummary"
         android:paddingStart="20dp"
         android:paddingBottom="10dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         app:layout_constraintWidth_percent="0.7"
         app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
@@ -66,6 +71,7 @@
         android:id="@+id/gear_icon"
         android:layout_width="0dp"
         android:layout_height="0dp"
+        android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
         app:layout_constraintEnd_toEndOf="@+id/gear_icon_image"
         app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 16aeb95..5d986e0 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -27,6 +27,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:paddingTop="24dp"
+        android:maxLines="1"
         android:ellipsize="end"
         android:gravity="center_vertical|center_horizontal"
         android:text="@string/quick_settings_bluetooth_label"
@@ -58,9 +59,12 @@
         style="@style/BluetoothTileDialog.Device"
         android:layout_width="0dp"
         android:layout_height="64dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         android:gravity="center_vertical"
         android:layout_marginTop="4dp"
         android:text="@string/turn_on_bluetooth"
+        android:clickable="false"
         android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
         android:textSize="16sp"
         app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
@@ -84,53 +88,17 @@
         app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title"
         app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" />
 
-    <androidx.constraintlayout.widget.Group
-        android:id="@+id/pair_new_device_layout_group"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:constraint_referenced_ids="ic_add,pair_new_device_text" />
-
-    <ImageView
-        android:id="@+id/ic_add"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginStart="36dp"
-        android:gravity="center_vertical"
-        android:importantForAccessibility="no"
-        android:src="@drawable/ic_add"
-        app:layout_constraintBottom_toTopOf="@id/device_list"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
-        app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title"
-        android:tint="?android:attr/textColorPrimary" />
-
-    <TextView
-        android:id="@+id/pair_new_device_text"
-        style="@style/BluetoothTileDialog.Device"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/bluetooth_dialog_device_height"
-        android:gravity="center_vertical"
-        android:layout_marginStart="0dp"
-        android:paddingStart="20dp"
-        android:text="@string/pair_new_bluetooth_devices"
-        android:textSize="14sp"
-        android:textAppearance="@style/TextAppearance.Dialog.Title"
-        app:layout_constraintBottom_toTopOf="@id/device_list"
-        app:layout_constraintStart_toEndOf="@+id/ic_add"
-        app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title"
-        app:layout_constraintEnd_toEndOf="parent" />
-
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/device_list"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
         android:nestedScrollingEnabled="false"
         android:overScrollMode="never"
         android:scrollbars="vertical"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/pair_new_device_text"
+        app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle"
         app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
 
     <androidx.constraintlayout.widget.Group
@@ -148,7 +116,7 @@
         android:importantForAccessibility="no"
         android:gravity="center_vertical"
         android:src="@drawable/ic_arrow_forward"
-        app:layout_constraintBottom_toTopOf="@+id/done_button"
+        app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/see_all_text"
         app:layout_constraintTop_toBottomOf="@id/device_list" />
@@ -157,18 +125,59 @@
         android:id="@+id/see_all_text"
         style="@style/BluetoothTileDialog.Device"
         android:layout_width="0dp"
-        android:layout_height="@dimen/bluetooth_dialog_device_height"
+        android:layout_height="64dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         android:gravity="center_vertical"
         android:layout_marginStart="0dp"
         android:paddingStart="20dp"
         android:text="@string/see_all_bluetooth_devices"
         android:textSize="14sp"
         android:textAppearance="@style/TextAppearance.Dialog.Title"
-        app:layout_constraintBottom_toTopOf="@+id/done_button"
+        app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
         app:layout_constraintStart_toEndOf="@+id/ic_arrow"
         app:layout_constraintTop_toBottomOf="@id/device_list"
         app:layout_constraintEnd_toEndOf="parent" />
 
+    <androidx.constraintlayout.widget.Group
+        android:id="@+id/pair_new_device_layout_group"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        app:constraint_referenced_ids="ic_add,pair_new_device_text" />
+
+    <ImageView
+        android:id="@+id/ic_add"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="36dp"
+        android:gravity="center_vertical"
+        android:importantForAccessibility="no"
+        android:src="@drawable/ic_add"
+        app:layout_constraintBottom_toTopOf="@id/done_button"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
+        app:layout_constraintTop_toBottomOf="@id/see_all_text"
+        android:tint="?android:attr/textColorPrimary" />
+
+    <TextView
+        android:id="@+id/pair_new_device_text"
+        style="@style/BluetoothTileDialog.Device"
+        android:layout_width="0dp"
+        android:layout_height="64dp"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:gravity="center_vertical"
+        android:layout_marginStart="0dp"
+        android:paddingStart="20dp"
+        android:text="@string/pair_new_bluetooth_devices"
+        android:textSize="14sp"
+        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        app:layout_constraintBottom_toTopOf="@id/done_button"
+        app:layout_constraintStart_toEndOf="@+id/ic_add"
+        app:layout_constraintTop_toBottomOf="@id/see_all_text"
+        app:layout_constraintEnd_toEndOf="parent" />
+
     <Button
         android:id="@+id/done_button"
         style="@style/Widget.Dialog.Button"
@@ -184,5 +193,5 @@
         android:text="@string/inline_done_button"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/see_all_text" />
+        app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" />
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index a51c55e..8cfcb68 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -15,8 +15,9 @@
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/cd_bottom_sheet"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="wrap_content"
     android:gravity="center_horizontal"
     android:orientation="vertical"
     android:paddingHorizontal="@dimen/dialog_side_padding"
@@ -26,11 +27,14 @@
 
     <ImageView
         android:id="@+id/connected_display_dialog_icon"
-        android:layout_width="@dimen/screenrecord_logo_size"
-        android:layout_height="@dimen/screenrecord_logo_size"
+        android:layout_width="@dimen/connected_display_dialog_logo_size"
+        android:layout_height="@dimen/connected_display_dialog_logo_size"
+        android:background="@drawable/circular_background"
+        android:backgroundTint="?androidprv:attr/materialColorPrimary"
         android:importantForAccessibility="no"
+        android:padding="6dp"
         android:src="@drawable/stat_sys_connected_display"
-        android:tint="?androidprv:attr/materialColorPrimary" />
+        android:tint="?androidprv:attr/materialColorOnPrimary" />
 
     <TextView
         android:id="@+id/connected_display_dialog_title"
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 8241070..ad9a775 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -115,8 +115,7 @@
             android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
             android:src="@drawable/dream_overlay_assistant_attention_indicator"
             android:visibility="gone"
-            android:contentDescription=
-                "@string/dream_overlay_status_bar_assistant_attention_indicator" />
+            android:contentDescription="@string/assistant_attention_content_description" />
 
     </LinearLayout>
 </com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5a70b79..452bc31 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -66,7 +66,7 @@
             <FrameLayout
                 android:id="@+id/status_bar_start_side_content"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_gravity="center_vertical|start"
                 android:clipChildren="false">
 
@@ -77,7 +77,7 @@
                      and DISABLE_NOTIFICATION_ICONS, respectively -->
                 <LinearLayout
                     android:id="@+id/status_bar_start_side_except_heads_up"
-                    android:layout_height="wrap_content"
+                    android:layout_height="match_parent"
                     android:layout_width="match_parent"
                     android:layout_gravity="center_vertical|start"
                     android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index acee425..7e03bd9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,24 +26,6 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.systemui.statusbar.BackDropView
-        android:id="@+id/backdrop"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        sysui:ignoreRightInset="true"
-    >
-        <ImageView android:id="@+id/backdrop_back"
-                   android:layout_width="match_parent"
-                   android:scaleType="centerCrop"
-                   android:layout_height="match_parent" />
-        <ImageView android:id="@+id/backdrop_front"
-                   android:layout_width="match_parent"
-                   android:layout_height="match_parent"
-                   android:scaleType="centerCrop"
-                   android:visibility="invisible" />
-    </com.android.systemui.statusbar.BackDropView>
-
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
@@ -63,7 +45,8 @@
     <com.android.systemui.statusbar.LightRevealScrim
         android:id="@+id/light_reveal_scrim"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        sysui:ignoreRightInset="true" />
 
     <include layout="@layout/status_bar_expanded"
              android:layout_width="match_parent"
@@ -83,6 +66,12 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
+    <ViewStub
+        android:id="@+id/communal_ui_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <include layout="@layout/brightness_mirror_container" />
 
     <com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 937e97a..f68dd7b 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Stoor tans skermskoot in werkprofiel …"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Skermkiekie is gestoor"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Kon nie skermkiekie stoor nie"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Toestel moet ontsluit word voordat skermkiekie gestoor kan word"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer weer skermkiekie neem"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan nie skermkiekie stoor nie"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Saai tans uit"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Onbenoemde toestel"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen toestelle beskikbaar nie"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi is nie gekoppel nie"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans vinnig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans stadig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klik die pylknoppie om die gemeenskaplike tutoriaal te begin"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Sinkroniseer skerm wedersyds"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Maak toe"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoon en kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Onlangse appgebruik"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sien onlangse toegang"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index de2fda4..20a8120 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ቅጽበታዊ ገፅ እይታን ወደ የስራ መገለጫ በማስቀመጥ ላይ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ቅጽበታዊ ገፅ ዕይታ ተቀምጧል"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ቅጽበታዊ ገፅ ዕይታን ማስቀመጥ አልተቻለም"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ቅጽበታዊ ገፅ ዕይታ ከመቀመጡ በፊት መሣሪያ መከፈት አለበት"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ቅጽበታዊ ገፅ ዕይታን እንደገና ማንሳት ይሞክሩ"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ቅጽበታዊ ገፅ እይታን ማስቀመጥ አልተቻለም"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"በመውሰድ ላይ"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ያልተሰየመ መሣሪያ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ምንም መሣሪያዎች አይገኙም"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi አልተገናኘም"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በፍጥነት ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
     <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="700342473477865107">"የጋራ አጋዥ ሥልጠናን ለመጀመር የቀስት አዝራሩ ላይ ጠቅ ያድርጉ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ማሳያን አንጸባርቅ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"አሰናብት"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ማይክሮፎን እና ካሜራ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"የቅርብ ጊዜ የመተግበሪያ አጠቃቀም"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"የቅርብ ጊዜ መዳረሻን አሳይ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 79b671c..03e019c 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في الملف الشخصي للعمل…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"تعذّر حفظ لقطة الشاشة"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"يجب أن يتم فتح قفل الجهاز قبل حفظ لقطة الشاشة."</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"جرّب أخذ لقطة الشاشة مرة أخرى"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"يتعذّر حفظ لقطة الشاشة."</string>
@@ -159,7 +161,7 @@
     <string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"النقش غير صحيح."</string>
     <string name="biometric_dialog_wrong_password" msgid="69477929306843790">"كلمة مرور غير صحيحة"</string>
     <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"تم إجراء عدد كبير جدًا من المحاولات غير الصحيحة.\nأعد المحاولة خلال <xliff:g id="NUMBER">%d</xliff:g> ثانية."</string>
-    <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"الطوارئ"</string>
+    <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"طوارئ"</string>
     <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"يُرجى إعادة المحاولة. المحاولة <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> من <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>"</string>
     <string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"سيتم حذف بياناتك"</string>
     <string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"عند إدخال نقش غير صحيح في المحاولة التالية، سيتم حذف بيانات هذا الجهاز."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"جارٍ الإرسال"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"جهاز لا يحمل اسمًا"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"لا يتوفر أي جهاز"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏لم يتم الاتصال بشبكة Wi-Fi."</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن سريعًا • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن ببطء • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"انقر على زر السهم لبدء الدليل التوجيهي العام."</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"بث المحتوى على الشاشة"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"إغلاق"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"الميكروفون والكاميرا"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"آخر استخدام في التطبيقات"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"عرض آخر استخدام في التطبيقات"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index ac30f18..014e3b7 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"কৰ্মস্থানৰ প্ৰ’ফাইলত স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্ৰীনশ্বট ছেভ কৰা হ’ল"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্ৰীনশ্বট ছেভ কৰিব পৰা নগ\'ল"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্ৰীনশ্বট ছেভ কৰিবলৈ ডিভাইচটো আনলক কৰিবই লাগিব"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"স্ক্ৰীনশ্বট আকৌ ল\'বলৈ চেষ্টা কৰক"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্ৰীনশ্বট ছেভ কৰিব নোৱাৰি"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"কাষ্টিং"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নাম নথকা ডিভাইচ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইচ নাই"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ৱাই-ফাইৰ সৈতে সংযোগ হৈ থকা নাই"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্ৰুতগতিৰে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • লাহে লাহে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
     <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="700342473477865107">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ কাঁড়চিহ্নৰ বুটামটোত ক্লিক কৰক"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ডিছপ্লে’ মিৰ’ৰ কৰক"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"অগ্ৰাহ্য কৰক"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্ৰ’ফ’ন আৰু কেমেৰা"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"শেহতীয়া এপৰ ব্যৱহাৰ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"শেহতীয়া এক্সেছ চাওক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 3ca61dc..d34ad9a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"İş profili skrinşotu saxlanılır…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinşot yadda saxlandı"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinşotu yadda saxlamaq alınmadı"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinşotu saxlamazdan əvvəl cihaz kiliddən çıxarılmalıdır"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skrinşotu yenidən çəkin"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinşotu yadda saxlamaq mümkün olmadı"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Yayım"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Heç bir cihaz əlçatan deyil"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi qoşulu deyil"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sürətlə şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Asta şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
     <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="700342473477865107">"İcma təlimatını başlatmaq üçün ox düyməsinə klikləyin"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyə əks etdirilsin?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Displeyi əks etdirin"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"İmtina edin"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon və kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son tətbiq istifadəsi"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son girişə baxın"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index b51d4b3..2b19e36 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Snimak ekrana se čuva na poslovnom profilu…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Čuvanje snimka ekrana nije uspelo"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora da bude otključan da bi snimak ekrana mogao da se sačuva"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probajte da ponovo napravite snimak ekrana"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Čuvanje snimka ekrana nije uspelo"</string>
@@ -101,8 +103,8 @@
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
     <string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Želite da započnete snimanje?"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
-    <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+    <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
     <string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Započni snimanje"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimaj zvuk"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk uređaja"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nije dostupan nijedan uređaj"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
     <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="700342473477865107">"Kliknite na dugme sa strelicom da biste započeli zajednički vodič"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -418,17 +422,17 @@
     <string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Jedna aplikacija"</string>
     <string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Delite ili snimite aplikaciju"</string>
     <string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Želite da počnete snimanje ili prebacivanje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
-    <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+    <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+    <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
     <string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Pokreni"</string>
     <string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je onemogućila ovu opciju"</string>
     <string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Želite da započnete prebacivanje?"</string>
-    <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
-    <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+    <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+    <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
     <string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Započni prebacivanje"</string>
     <string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Želite da počnete da delite?"</string>
-    <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
-    <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+    <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+    <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
     <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deljenje se zaustavlja kada menjate aplikacije"</string>
     <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deli ovu aplikaciju"</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li da preslikate na spoljnji ekran?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno koristila aplikacija"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 35a84b0..301a042 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Захаванне здымка экрана ў працоўны профіль…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Здымак экрана захаваны"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Не атрымалася зрабіць здымак экрана"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Перад захаваннем здымка экрана трэба разблакіраваць прыладу"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Паспрабуйце зрабіць здымак экрана яшчэ раз"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не ўдалося захаваць здымак экрана"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Ідзе перадача"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Прылада без назвы"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма даступных прылад"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Няма падключэння да Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе хуткая зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе павольная зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Націсніце кнопку са стрэлкай, каб азнаёміцца з дапаможнікам"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Адлюстраваць дысплэй"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыць"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрафон і камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нядаўна выкарыстоўваліся праграмамі"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Паглядзець нядаўні доступ"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 6b212e5..29f9d9e9 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Екранната снимка се запазва в служебния профил…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Екранната снимка е запазена"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Не можа да се запази екранна снимка"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"За да бъде запазена екранната снимка, устройството трябва да бъде отключено"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Опитайте да направите екранна снимка отново"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Екранната снимка не може да се запази"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Предава се"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Устройство без име"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма налични устройства"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Не е установена връзка с Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инвертиране на цветовете"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бързо • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бавно • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
     <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="700342473477865107">"Кликнете върху бутона със стрелка, за да стартирате общия урок"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Огледално копиране на дисплея"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Отхвърляне"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Скорошно използване на приложението"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Вижте скорошния достъп"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 5b5ac73..c2662d1 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"অফিস প্রোফাইলে স্ক্রিনশট সেভ করা হচ্ছে…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্রিনশট সেভ করা হয়েছে"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্রিনশট সেভ করা যায়নি"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্রিনশট সেভ করার আগে ডিভাইসটি অবশ্যই আনলক করতে হবে"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"আবার স্ক্রিনশট নেওয়ার চেষ্টা করুন"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্রিনশট সেভ করা যায়নি"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"কাস্ট করা হচ্ছে"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নামবিহীন ডিভাইস"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইস উপলব্ধ নয়"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ওয়াই-ফাই কানেক্ট করা নেই"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্রুত চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ধীরে চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
     <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="700342473477865107">"কমিউনিটি টিউটোরিয়াল চালু করতে তীরচিহ্ন বোতামে ক্লিক করুন"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ডিসপ্লে দেখান"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"বাতিল করুন"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্রোফোন ও ক্যামেরা"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"সম্প্রতি ব্যবহার করা অ্যাপ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"সাম্প্রতিক অ্যাক্সেস দেখুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 33330b7..8d10454 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pohranjivanje snimka ekrana na radni profil…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Nije moguće sačuvati snimak ekrana"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Morate otključati uređaj da možete sačuvati snimak ekrana"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo snimiti ekran"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće sačuvati snimak ekrana"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi mreža nije povezana"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boja"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Kliknite na dugme sa strelicom da pokrenete zajednički vodič"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno korištenje aplikacije"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 45bb342..683c034 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"S\'està desant la captura al perfil de treball…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"S\'ha desat la captura de pantalla"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"No s\'ha pogut desar la captura de pantalla"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositiu ha d\'estar desbloquejat abans que la captura de pantalla es pugui desar"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prova de tornar a fer una captura de pantalla"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No es pot desar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"En emissió"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositiu sense nom"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hi ha cap dispositiu disponible."</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant ràpidament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Fes clic al botó de la fletxa per iniciar el tutorial de la comunitat"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vols replicar-ho a la pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ignora"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micròfon i càmera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ús recent de l\'aplicació"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Mostra l\'accés recent"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 0d7d23a..cbbf9df 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukládání snímku obrazovky do pracovního profilu…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Snímek obrazovky byl uložen"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Snímek obrazovky se nepodařilo uložit"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Aby bylo možné uložit screenshot, zařízení musí být odemknuto"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zkuste snímek pořídit znovu"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímek obrazovky se nepodařilo uložit"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Odesílání"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepojmenované zařízení"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nejsou dostupná žádná zařízení"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Není připojena Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Rychlé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Pomalé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Kliknutím na tlačítko s šipkou spustíte společný výukový program"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
     <string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Zavřít"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon a fotoaparát"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávné použití aplikacemi"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobrazit nedávný přístup"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 4f6b88e..57124bd 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gemmer screenshot på din arbejdsprofil…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshottet blev gemt"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshottet kunne ikke gemmes"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheden skal være låst op, før du kan gemme screenshots"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv at tage et screenshot igen"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Dit screenshot kunne ikke gemmes."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Caster"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhed uden navn"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Der er ingen tilgængelige enheder"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Manglende Wi-Fi-forbindelse"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader hurtigt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader langsomt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klik på pilen for at starte den fælles vejledning"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Luk"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Seneste brug af apps"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se seneste adgang"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 07aa14f..8c308b9 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot wird in Arbeitsprofil gespeichert…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot gespeichert"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshot konnte nicht gespeichert werden"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Damit Screenshots gespeichert werden können, muss das Gerät entsperrt sein"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Versuche noch einmal, den Screenshot zu erstellen"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Screenshot kann nicht gespeichert werden"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Wird übertragen"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unbenanntes Gerät"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Keine Geräte verfügbar"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WLAN nicht verbunden"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string>
@@ -395,6 +398,8 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird schnell geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird langsam geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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>
+    <!-- no translation found for communal_tutorial_indicator_text (700342473477865107) -->
+    <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
@@ -1183,10 +1188,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
     <string name="install_app" msgid="5066668100199613936">"App installieren"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Schließen"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kürzliche App-Nutzung"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kürzliche Zugriffe ansehen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2a8e51c..69d877b 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Αποθήκευση στιγμιότ. οθόνης στο προφίλ εργασίας…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Το στιγμιότυπο οθόνης αποθηκεύτηκε"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Μη δυνατή αποθήκευση του στιγμιότυπου οθόνης"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Η συσκευή πρέπει να ξεκλειδωθεί για να αποθηκευτεί το στιγμιότυπο οθόνης."</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Δοκιμάστε να κάνετε ξανά λήψη του στιγμιότυπου οθόνης"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Δεν είναι δυνατή η αποθήκευση στιγμιότυπου οθόνης."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Μετάδοση"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ανώνυμη συσκευή"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Δεν υπάρχουν διαθέσιμες συσκευές"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Το Wi-Fi δεν είναι συνδεδεμένο"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Γρήγορη φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Αργή φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Κάντε κλικ στο κουμπί βέλους για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Κατοπτρισμός οθόνης"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Παράβλεψη"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Μικρόφωνο και Κάμερα"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Πρόσφατη χρήση εφαρμογής"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Εμφάνιση πρόσφατης πρόσβασης"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 8647adb..645f70e 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -1183,10 +1187,8 @@
     <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="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 0e282ff..f05d3c0 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -76,6 +76,7 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+    <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string>
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +281,7 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+    <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"No Wi‑Fi or Ethernet connection"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
@@ -395,6 +396,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 8647adb..645f70e 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -1183,10 +1187,8 @@
     <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="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 8647adb..645f70e 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -1183,10 +1187,8 @@
     <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="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 1f3ab27..4934d73 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -76,6 +76,7 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎Saving screenshot to work profile…‎‏‎‎‏‎"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎Screenshot saved‎‏‎‎‏‎"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎Couldn\'t save screenshot‎‏‎‎‏‎"</string>
+    <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎External Display‎‏‎‎‏‎"</string>
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‎Device must be unlocked before screenshot can be saved‎‏‎‎‏‎"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‏‎Try taking screenshot again‎‏‎‎‏‎"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎Can\'t save screenshot‎‏‎‎‏‎"</string>
@@ -280,7 +281,7 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎Casting‎‏‎‎‏‎"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‎‎Unnamed device‎‏‎‎‏‎"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎No devices available‎‏‎‎‏‎"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎Wi‑Fi not connected‎‏‎‎‏‎"</string>
+    <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎No Wi‑Fi or Ethernet connection‎‏‎‎‏‎"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎Brightness‎‏‎‎‏‎"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎Color inversion‎‏‎‎‏‎"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎Color correction‎‏‎‎‏‎"</string>
@@ -395,6 +396,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging rapidly • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging slowly • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <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="700342473477865107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎Click on the arrow button to start the communal tutorial‎‏‎‎‏‎"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎Switch user‎‏‎‎‏‎"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎pulldown menu‎‏‎‎‏‎"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎All apps and data in this session will be deleted.‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index d3ea858..fa1a3f5 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando cap. de pantalla en perfil de trabajo…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Se guardó la captura de pantalla"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"No se pudo guardar la captura de pantalla"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe estar desbloqueado para poder guardar la captura de pantalla"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a hacer una captura de pantalla"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se pudo guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Transmitiendo"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Red Wi-Fi no conectada"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corregir colores"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lento • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Haz clic en el botón de flecha para iniciar el instructivo comunal"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar a la pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Descartar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en apps"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver accesos recientes"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 284fad4..005895b 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando captura en el perfil de trabajo…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Se ha guardado la captura de pantalla"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"No se ha podido guardar la captura de pantalla"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe desbloquearse para que se pueda guardar la captura de pantalla"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a intentar hacer la captura de pantalla"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se puede guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Enviando"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi no conectado"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
     <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="700342473477865107">"Haz clic en el botón de la flecha para iniciar el tutorial de la comunidad"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Replicar en pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Replicar pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Cerrar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en aplicaciones"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso reciente"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 2b79ee5..b4463d2 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekraanipildi salvestamine tööprofiilile …"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekraanipilt salvestati"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekraanipilti ei õnnestunud salvestada"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enne ekraanipildi salvestamist tuleb seade avada"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Proovige ekraanipilt uuesti jäädvustada"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekraanipilti ei saa salvestada"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Osatäitjad"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimeta seade"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ühtegi seadet pole saadaval"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi-ühendus puudub"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kiirlaadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Aeglane laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
     <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="700342473477865107">"Ühise õpetuse käivitamiseks klõpsake noolenuppu"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
     <string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Ekraani peegeldamine"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Loobu"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ja kaamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Rakenduste hiljutine kasutamine"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kuva hiljutine juurdepääs"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 68698ea..6701a77 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pantaila-argazkia laneko profilean gordetzen…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Gorde da pantaila-argazkia"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ezin izan da gorde pantaila-argazkia"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pantaila-argazkia gordetzeko, gailuak desblokeatuta egon beharko du"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Saiatu berriro pantaila-argazkia ateratzen"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ezin da gorde pantaila-argazkia"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Igortzen"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Izenik gabeko gailua"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ez dago gailurik erabilgarri"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ez zaude konektatuta wifi-sarera"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Koloreen alderantzikatzea"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Bizkor kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mantso kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
     <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="700342473477865107">"Tutorial komuna hasteko, sakatu geziaren botoia"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
@@ -676,8 +680,8 @@
     <string name="group_system_go_back" msgid="8838454003680364227">"Atzera: itzuli aurreko egoerara (atzera egiteko botoia)"</string>
     <string name="group_system_access_home_screen" msgid="1857344316928441909">"Atzitu hasierako pantaila"</string>
     <string name="group_system_overview_open_apps" msgid="6897128761003265350">"Ikusi irekitako aplikazioen ikuspegi orokorra"</string>
-    <string name="group_system_cycle_forward" msgid="9202444850838205990">"Joan azken aplikazioetako batetik bestera (aurrera)"</string>
-    <string name="group_system_cycle_back" msgid="5163464503638229131">"Joan azken aplikazioetako batetik bestera (atzera)"</string>
+    <string name="group_system_cycle_forward" msgid="9202444850838205990">"Joan azkenaldian erabilitako aplikazio batetik bestera (aurrera)"</string>
+    <string name="group_system_cycle_back" msgid="5163464503638229131">"Joan azkenaldian erabilitako aplikazio batetik bestera (atzera)"</string>
     <string name="group_system_access_all_apps_search" msgid="488070738028991753">"Atzitu aplikazio guztien zerrenda eta bilatu (adibidez, bilatzeko aukeraren edo Exekutatzeko tresna aplikazioaren bidez)"</string>
     <string name="group_system_hide_reshow_taskbar" msgid="3809304065624351131">"Ezkutatu eta erakutsi (berriro) zereginen barra"</string>
     <string name="group_system_access_system_settings" msgid="7961639365383008053">"Atzitu sistemaren ezarpenak"</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Baztertu"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonoa eta kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikazioen azken erabilera"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ikusi azkenaldiko sarbidea"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 7199240..705cc7c 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"درحال ذخیره کردن نماگرفت در نمایه کاری…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"نماگرفت ذخیره شد"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"نماگرفت ذخیره نشد"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"برای ذخیره کردن نماگرفت، قفل دستگاه باید باز باشد"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوباره نماگرفت بگیرید"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"نماگرفت ذخیره نمی‌شود"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"در حال فرستادن"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"دستگاه بدون نام"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"دستگاهی موجود نیست"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏Wi-Fi وصل نیست"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن سریع • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن آهسته • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
     <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="700342473477865107">"برای شروع آموزش گام‌به‌گام عمومی، روی دکمه جهت‌نما کلیک کنید"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایین‌پر"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامه‌ها و داده‌های این جلسه حذف خواهد شد."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"بازتاباندن صفحه‌نمایش"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"بستن"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"میکروفون و دوربین"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"استفاده اخیر از برنامه"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"دیدن دسترسی اخیر"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 073ea60..d1c583b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Kuvakaappausta tallennetaan työprofiiliin…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Kuvakaappaus tallennettu"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Kuvakaappauksen tallennus epäonnistui"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Laitteen lukitus täytyy avata ennen kuin kuvakaappaus voidaan tallentaa"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Yritä ottaa kuvakaappaus uudelleen."</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kuvakaappausta ei voi tallentaa"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Lähetetään"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimetön laite"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Laitteita ei ole käytettävissä"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fiä ei ole yhdistetty"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu nopeasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu hitaasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
     <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="700342473477865107">"Aloita yhteisöesittely klikkaamalla nuolipainiketta"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
     <string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ohita"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni ja kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Sovellusten viimeaikainen käyttö"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Katso viimeaikainen käyttö"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 52bcd76..366d715 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sauv. de la capture dans le profil prof. en cours…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"L\'appareil doit être déverrouillé avant qu\'une capture d\'écran puisse être enregistrée"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de faire une autre capture d\'écran"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil à proximité"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Non connecté au Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Cliquez sur le bouton de flèche pour démarrer le tutoriel communautaire"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone et appareil photo"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par les applications"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Afficher l\'accès récent"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 4362d3c..8a51092 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Enregistrement de capture d\'écran dans profil pro…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Vous devez déverrouiller l\'appareil pour que la capture d\'écran soit enregistrée"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de nouveau de faire une capture d\'écran"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil disponible."</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi non connecté"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge rapide • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Cliquer sur la flèche pour démarrer le tutoriel collectif"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirroring sur écran externe ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micro et caméra"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par les applis"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consulter les accès récents"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 1b836a8..e8aa65f 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gardando captura de pantalla no perfil de traballo"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Gardouse a captura de pantalla"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Non se puido gardar a captura de pantalla"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que se poida gardar a captura de pantalla, o dispositivo debe estar desbloqueado"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Volve tentar crear unha captura de pantalla"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Non se puido gardar a captura de pantalla"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Emitindo"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sen nome"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Non hai dispositivos dispoñibles"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"A wifi non está conectada"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando rapidamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lentamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Fai clic no botón da frecha para iniciar o titorial comunitario"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Replicar pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Pechar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono e cámara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente por parte de aplicacións"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso recente"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 132ee2e..15b1b44 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ઑફિસની પ્રોફાઇલમાં સ્ક્રીનશૉટ સાચવી રહ્યાં છીએ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"સ્ક્રીનશૉટ સાચવ્યો"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"સ્ક્રીનશૉટ સાચવી શક્યાં નથી"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"સ્ક્રીનશૉટ સાચવવામાં આવે તે પહેલાં ડિવાઇસને અનલૉક કરવું જરૂરી છે"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ફરીથી સ્ક્રીનશૉટ લેવાનો પ્રયાસ કરો"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"સ્ક્રીનશૉટ સાચવી શકાતો નથી"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"કાસ્ટ કરી રહ્યાં છે"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"અનામાંકિત ઉપકરણ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"કોઈ ઉપકરણો ઉપલબ્ધ નથી"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"વાઇ-ફાઇ કનેક્ટ નથી"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ઝડપથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ધીમેથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
     <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="700342473477865107">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ઍરો બટન પર ક્લિક કરો"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"મિરર ડિસ્પ્લે"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"છોડી દો"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"માઇક્રોફોન અને કૅમેરા"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"તાજેતરનો ઍપનો વપરાશ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"તાજેતરનો ઍક્સેસ મેનેજ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f942de5..d63f5cb 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"स्क्रीनशॉट, वर्क प्रोफ़ाइल में सेव किया जा रहा है…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव किया गया"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव नहीं किया जा सका"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव करने के लिए डिवाइस का अनलॉक होना ज़रूरी है"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट दोबारा लेने की कोशिश करें"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट को सेव नहीं किया जा सकता"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"कास्टिंग"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"अनाम डिवाइस"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोई डिवाइस उपलब्ध नहीं"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाई-फ़ाई कनेक्ट नहीं है"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • तेज़ चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • धीरे चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
     <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="700342473477865107">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, तीर के निशान वाले बटन पर क्लिक करें"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"मिरर डिसप्ले"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"खारिज करें"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफ़ोन और कैमरा"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"हाल ही में इस्तेमाल करने वाला ऐप्लिकेशन"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हाल में ऐक्सेस करने वाले ऐप"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index f7c68e1..c3ea5ff2 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Spremanje snimke zaslona na poslovni profil…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimka zaslona spremljena"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Snimka zaslona nije spremljena"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora biti otključan da bi se snimka zaslona mogla spremiti"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo napraviti snimku zaslona"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće spremiti snimku zaslona"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Emitiranje"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Uređaj bez naziva"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi mreža nije povezana"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • brzo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • sporo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
     <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="700342473477865107">"Kliknite gumb sa strelicom da biste pokrenuli zajednički vodič"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li zrcaliti na vanjski zaslon?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna upotreba aplikacije"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Pogledajte nedavni pristup"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index c1b38d9..c894232 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Képernyőkép mentése a munkaprofilba…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"A képernyőkép mentése sikerült"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Nem sikerült a képernyőkép mentése"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Az eszközt fel kell oldani a képernyőkép mentése előtt"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Próbálja meg újra elkészíteni a képernyőképet"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nem lehetséges a képernyőkép mentése"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Átküldés"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Név nélküli eszköz"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nem áll rendelkezésre eszköz"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nem kapcsolódik Wi‑Fi-hálózathoz"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Gyors töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lassú töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Kattintson a nyíl gombra a közösségi útmutató elindításához"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string>
     <string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzőt a külső képernyőre?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Kijelző tükrözése"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Elvetés"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon és kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Legutóbbi alkalmazáshasználat"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Legutóbbi hozzáférés"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 7139a1f..67d3886 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Սքրինշոթը պահվում է աշխատանքային պրոֆիլում…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Սքրինշոթը պահվեց"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Չհաջողվեց պահել սքրինշոթը"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Սքրինշոթը պահելու համար ապակողպեք սարքը։"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Փորձեք նորից"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Չհաջողվեց պահել սքրինշոթը"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Հեռարձակում"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Անանուն սարք"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Հասանելի սարքեր չկան"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-ը միացված չէ"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Արագ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Դանդաղ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Սեղմեք սլաքի կոճակը՝ ուղեցույցը գործարկելու համար"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Հայելապատճենել էկրանը"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Փակել"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Խոսափող և տեսախցիկ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Հավելվածի վերջին օգտագործումը"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Տեսնել վերջին օգտագործումը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 44aafde..b1feb48 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan screenshot ke profil kerja …"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot disimpan"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan screenshot"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Perangkat harus dibuka kuncinya agar screenshot dapat disimpan"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Coba ambil screenshot lagi"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan screenshot"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Melakukan transmisi"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Perangkat tanpa nama"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Perangkat tak tersedia"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi tidak terhubung"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan cepat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan lambat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klik tombol panah untuk memulai tutorial komunal"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Tutup"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan aplikasi baru-baru ini"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaru"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 1fda572..9ff9c1a 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Vistar skjámynd á vinnusnið…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Skjámynd vistuð"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekki var hægt að vista skjámynd"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Taka verður tækið úr lás áður en hægt er að vista skjámynd"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prófaðu að taka skjámynd aftur"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekki er hægt að vista skjámynd"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Sendir út"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ónefnt tæki"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Engin tæki til staðar"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ekki tengt"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hraðhleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hæg hleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Smelltu á örvahnappinn til að hefja samfélagsleiðsögnina"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
     <string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Hunsa"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Hljóðnemi og myndavél"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nýlega notað af forriti"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sjá nýlegan aðgang"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index ed8885f..7fafa62 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvataggio screenshot nel profilo di lavoro…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossibile salvare lo screenshot"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"È necessario sbloccare il dispositivo per poter salvare lo screenshot"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Riprova ad acquisire lo screenshot"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossibile salvare lo screenshot"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"In trasmissione"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo senza nome"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nessun dispositivo disponibile"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nessuna connessione Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica veloce • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica lenta • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
     <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="700342473477865107">"Fai clic sul pulsante Freccia per iniziare il tutorial della community"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
     <string name="install_app" msgid="5066668100199613936">"Installa app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Chiudi"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfono e fotocamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente da app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vedi accesso recente"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 51f5452..ced5895 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"צילום המסך נשמר בפרופיל העבודה…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"צילום המסך נשמר"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"לא ניתן היה לשמור את צילום המסך"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"כדי שצילום המסך יישמר, צריך לבטל את הנעילה של המכשיר"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"אפשר לצלם שוב את המסך"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"לא ניתן לשמור את צילום המסך"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"‏מתבצעת העברה (cast)"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"מכשיר ללא שם"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"אין מכשירים זמינים"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏אין חיבור ל-Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה מהירה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
     <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="700342473477865107">"אפשר ללחוץ על לחצן החץ כדי להפעיל את המדריך המשותף"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"תצוגת מראה"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"סגירה"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"מיקרופון ומצלמה"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"נעשה שימוש לאחרונה באפליקציות"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"צפייה בהרשאות הגישה האחרונות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index e77aed9..f5d5b74 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"スクリーンショットを仕事用プロファイルに保存中…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"スクリーンショットを保存しました"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"スクリーンショット保存エラー"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"スクリーンショットを保存するには、デバイスのロックを解除する必要があります"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"スクリーンショットを撮り直してください"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"スクリーンショットを保存できません"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"キャストしています"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"名前のないデバイス"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"利用可能なデバイスがありません"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi 未接続"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 急速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 低速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"矢印ボタンをクリックすると、コミュニティ チュートリアルが開始します"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ディスプレイをミラーリングする"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"閉じる"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"マイクとカメラ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近のアプリの使用状況"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"最近のアクセスを表示"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 8d7deec..ae93966 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"მიმდინარეობს ეკრანის ანაბეჭდის შენახვა სამუშაო პროფილში…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ეკრანის ანაბეჭდი შენახულია"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ეკრანის ანაბეჭდის შენახვა ვერ მოხერხდა"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"მოწყობილობა უნდა განიბლოკოს ეკრანის ანაბეჭდის შენახვა რომ შეძლოთ"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ხელახლა ცადეთ ეკრანის ანაბეჭდის გაკეთება"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ეკრანის ანაბეჭდის შენახვა ვერ ხერხდება"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"გადაიცემა"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"უსახელო მოწყობილობა"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"მოწყობილობები მიუწვდომელია"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi არ არის დაკავშირებული"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • სწრაფად იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ნელა იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"დააწკაპუნეთ ისრის ღილაკზე, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ეკრანის არეკვლა"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"დახურვა"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"მიკროფონი და კამერა"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"აპის ბოლოდროინდელი გამოყენება"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ბოლო წვდომის ნახვა"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index fc4fe8a..8c8efd1 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жұмыс профиліне сақталып жатыр…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сақталды"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сақталмады"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншот сақталуы үшін, құрылғы құлпын ашу керек."</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Қайта скриншот жасап көріңіз"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншотты сақтау мүмкін емес."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Трансляциялануда"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Атаусыз құрылғы"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Құрылғылар қол жетімді емес"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi желісіне жалғанбаған"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жылдам зарядтау • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Баяу зарядталуда • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
     <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="700342473477865107">"Жалпы оқулықты ашу үшін бағыт түймесін басыңыз."</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Айна дисплей"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Қабылдамау"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон және камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Соңғы рет қолданбаның датчикті пайдалануы"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Соңғы рет пайдаланғандар"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 10c291d..cefdd27 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"កំពុងរក្សាទុករូបថតអេក្រង់ទៅកម្រងព័ត៌មានការងារ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"បានរក្សាទុក​រូបថតអេក្រង់"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"មិន​អាច​រក្សាទុក​រូបថត​អេក្រង់បានទេ"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ត្រូវតែ​ដោះសោ​ឧបករណ៍​ជាមុនសិន ទើបអាច​រក្សាទុក​រូបថតអេក្រង់​បាន"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"សាកល្បង​ថតរូបថត​អេក្រង់​ម្តងទៀត"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"មិនអាច​រក្សាទុករូបថត​អេក្រង់បានទេ"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"ការ​ចាត់​ថ្នាក់"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ឧបករណ៍​​ដែល​មិន​មាន​ឈ្មោះ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"មិន​មាន​ឧបករណ៍​ដែល​អាច​ប្រើ​បាន"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"មិនមាន​ការតភ្ជាប់ Wi-Fi ទេ"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាស​ពណ៌"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការ​កែតម្រូវ​ពណ៌"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុង​សាកថ្មយ៉ាង​ឆាប់រហ័ស • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុង​សាកថ្ម​យឺត • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"ចុចលើប៊ូតុងសញ្ញាព្រួញ ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរ​អ្នក​ប្រើ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយ​ទាញចុះ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យ​ទាំងអស់​ក្នុង​វគ្គ​នេះ​នឹង​ត្រូវ​លុប។"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"បញ្ចាំងទៅផ្ទាំងអេក្រង់"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ច្រានចោល"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"មីក្រូហ្វូន និងកាមេរ៉ា"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ការប្រើប្រាស់កម្មវិធីថ្មីៗនេះ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"មើលការចូលប្រើនាពេលថ្មីៗនេះ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 7716980..6ec02ca 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ಗೆ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಬೇಕು"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಪುನಃ ತೆಗೆದುಕೊಳ್ಳಲು ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ಹೆಸರಿಸದಿರುವ ಸಾಧನ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ಯಾವುದೇ ಸಾಧನಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್‍ವರ್ಶನ್"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ವೇಗವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
     <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="700342473477865107">"ಸಮುದಾಯದ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಆ್ಯರೋ ಬಟನ್ ಅನ್ನು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್‌ಡೌನ್ ಮೆನು"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್‌ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ಮಿರರ್ ಡಿಸ್‌ಪ್ಲೇ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ವಜಾಗೊಳಿಸಿ"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಕ್ಯಾಮರಾ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ಇತ್ತೀಚಿನ ಆ್ಯಪ್ ಬಳಕೆ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ಇತ್ತೀಚಿನ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನೋಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index c2fbf09..69525ee 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"직장 프로필에 스크린샷 저장 중…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"스크린샷 저장됨"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"스크린샷을 저장할 수 없음"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"스크린샷을 저장하려면 기기를 잠금 해제해야 합니다."</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"스크린샷을 다시 찍어 보세요."</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"스크린샷을 저장할 수 없습니다."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"전송 중"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"이름이 없는 기기"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"사용 가능한 기기가 없습니다."</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi가 연결되지 않음"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 고속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 저속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
     <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="700342473477865107">"공동 튜토리얼을 시작하려면 화살표 버튼을 클릭하세요."</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"디스플레이 미러링"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"닫기"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"마이크 및 카메라"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"최근 앱 사용"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"최근 액세스 보기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 8ab4cb7..400afed 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жумуш профилине сакталууда…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сакталды"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сакталган жок"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншотту сактоо үчүн түзмөктүн кулпусун ачуу керек"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Скриншотту кайра тартып көрүңүз"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншот сакталган жок"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Тышкы экранга чыгарылууда"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Аты жок түзмөк"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Жеткиликтүү түзмөктөр жок"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi туташкан жок"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстөрдү инверсиялоо"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түстөрдү тууралоо"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Тез кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жай кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
     <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="700342473477865107">"Жалпы үйрөткүчтү иштетүү үчүн жебе баскычын басыңыз"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Тышкы экран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Жабуу"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон жана камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Жакында колдонмолордо иштетилген"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Акыркы пайдалануусун көрүү"</string>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 587caaf..db526b1 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -46,4 +46,7 @@
      For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
      TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">true</bool>
+
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index a1585f2..b66e6dc 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ກຳລັງບັນທຶກຮູບໜ້າຈໍໃສ່ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ບັນທຶກຮູບໜ້າຈໍໄວ້ແລ້ວ"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ຈະຕ້ອງປົດລັອກອຸປະກອນກ່ອນບັນທຶກຮູບໜ້າຈໍ"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ກະລຸນາລອງຖ່າຍຮູບໜ້າຈໍອີກຄັ້ງ"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"​ກຳ​ລັງ​ສົ່ງ​ສັນ​ຍານ"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"​ອຸ​ປະ​ກອນບໍ່​ມີ​ຊື່"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"​ບໍ່​ມີ​ອຸ​ປະ​ກອນ​ທີ່​ສາ​ມາດ​ໃຊ້​ໄດ້"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບໄວ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບຊ້າ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"ຄລິກໃສ່ປຸ່ມລູກສອນເພື່ອເລີ່ມຕົ້ນສອນການນຳໃຊ້ຊຸມຊົນ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯ​ແລະ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ໃນ​ເຊດ​ຊັນ​ນີ້​ຈະ​ຖືກ​ລຶບ​ອອກ."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ຈໍສະແດງຜົນແບບສະທ້ອນ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ປິດໄວ້"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ໄມໂຄຣໂຟນ ແລະ ກ້ອງຖ່າຍຮູບ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ການໃຊ້ແອັບຫຼ້າສຸດ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ເບິ່ງສິດເຂົ້າເຖິງຫຼ້າສຸດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index c5f649c..955fe67 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Išsaugoma ekrano kopija darbo profilyje…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrano kopija išsaugota"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrano kopijos išsaugoti nepavyko"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Įrenginys turi būti atrakintas, kad būtų galima išsaugoti ekrano kopiją"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pabandykite padaryti ekrano kopiją dar kartą"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekrano kopijos išsaugoti nepavyko"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Perduodama"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Įrenginys be pavadinimo"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nėra pasiekiamų įrenginių"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"„Wi-Fi“ neprijungtas"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sparčiai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lėtai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
     <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="700342473477865107">"Spustelėkite rodyklės mygtuką, kad paleistumėte bendruomenės mokomąją medžiagą"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string>
     <string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdą išoriniame ekrane?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdą"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Atsisakyti"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonas ir fotoaparatas"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Pastarasis programos naudojimas"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Žr. pastarąją prieigą"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index d493609..16cef4b 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Notiek ekrānuzņēmuma saglabāšana darba profilā…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrānuzņēmums saglabāts"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrānuzņēmumu neizdevās saglabāt."</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Lai varētu saglabāt ekrānuzņēmumu, ierīcei ir jābūt atbloķētai."</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Mēģiniet izveidot jaunu ekrānuzņēmumu."</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nevar saglabāt ekrānuzņēmumu"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Notiek apraide…"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nenosaukta ierīce"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nav pieejamu ierīču."</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nav izveidots savienojums ar Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ātrā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lēnā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Noklikšķiniet uz bultiņas pogas, lai palaistu kopienas pamācību."</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string>
     <string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguļot ārējā displejā?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spoguļot displeju"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Nerādīt"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofons un kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nesen izmantoja lietotnes"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Skatīt neseno piekļuvi"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 46193b3..8babe8c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Се зачувува слика од екранот на вашиот работен профил…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Не може да се зачува слика од екранот"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уредот мора да биде отклучен за да може да се зачува слика од екранот"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Повторно обидете се да направите слика од екранот"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не може да се зачува слика од екранот"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Емитување"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименуван уред"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нема достапни уреди"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нема Wi-Fi врска"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни брзо • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни бавно • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Кликнете на копчето со стрелка за да го започнете заедничкото упатство"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Отфрли"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Неодамнешно користење на апликација"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Видете го скорешниот пристап"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index d631538..c5b6536 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"സ്‌ക്രീൻഷോട്ട് സംരക്ഷിച്ചു"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"സ്‌ക്രീൻഷോട്ട് സംരക്ഷിക്കാനായില്ല"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നതിന് മുമ്പ് ഉപകരണം അൺലോക്ക് ചെയ്തിരിക്കണം"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"സ്‌ക്രീൻഷോട്ട് എടുക്കാൻ വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"സ്‌ക്രീൻഷോട്ട് സംരക്ഷിക്കാനാകുന്നില്ല"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"കാസ്റ്റുചെയ്യുന്നു"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"പേരിടാത്ത ഉപകരണം"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ഉപകരണങ്ങളൊന്നും ലഭ്യമല്ല"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്‌തിട്ടില്ല"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • വേഗത്തിൽ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
     <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="700342473477865107">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ അമ്പടയാള ബട്ടണിൽ ക്ലിക്ക് ചെയ്യുക"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"മിറർ ഡിസ്‌പ്ലേ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"മൈക്രോഫോണും ക്യാമറയും"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"അടുത്തിടെയുള്ള ആപ്പ് ഉപയോഗം"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"അടുത്തിടെയുള്ള ആക്‌സസ് കാണുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index b16b22a..b083c66 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Дэлгэцийн агшныг ажлын профайлд хадгалж байна…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Дэлгэцээс дарсан зургийг хадгалсан"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Дэлгэцээс дарсан зургийг хадгалж чадсангүй"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Дэлгэцийн агшныг хадгалах боломжтой болохоос өмнө төхөөрөмжийн түгжээг тайлах ёстой"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Дэлгэцийн зургийг дахин дарж үзнэ үү"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Дэлгэцийн агшныг хадгалах боломжгүй"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Дамжуулж байна"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Нэргүй төхөөрөмж"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Төхөөрөмж байхгүй"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-д холбогдоогүй байна"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө хувиргалт"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгө тохируулга"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Хурдтай цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Удаан цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
     <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="700342473477865107">"Нийтийн практик хичээлийг эхлүүлэхийн тулд суман товчийг товшино уу"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Дэлгэцийн тусгал үүсгэх"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Хаах"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон болон камер"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Аппын саяхны ашиглалт"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Саяхны хандалтыг харах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 16fac23..5d9f67e 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलवर स्क्रीनशॉट सेव्ह करत आहे…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव्ह केला"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव्ह करू शकलो नाही"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव्ह करण्याआधी डिव्हाइस अनलॉक करणे आवश्यक आहे"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट पुन्हा घेण्याचा प्रयत्न करा"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट सेव्ह करू शकत नाही"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"कास्ट करत आहे"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"निनावी डिव्हाइस"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोणतेही डिव्हाइसेस उपलब्ध नाहीत"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाय-फाय नाही"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • वेगाने चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • हळू चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
     <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="700342473477865107">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी ॲरो बटणावर क्लिक करा"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अ‍ॅप्स आणि डेटा हटवला जाईल."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर करा"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"डिसमिस करा"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"मायक्रोफोन आणि कॅमेरा"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"अलीकडील अ‍ॅप वापर"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"अलीकडील अ‍ॅक्सेस पहा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 7cafdcd..10e9859c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan tangkapan skrin ke profil kerja…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Tangkapan skrin disimpan"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan tangkapan skrin"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Peranti mesti dibuka kunci sebelum tangkapan skrin dapat disimpan"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Cuba ambil tangkapan skrin sekali lagi"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan tangkapan skrin"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Menghantar"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Peranti tidak bernama"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Tiada peranti tersedia"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tidak disambungkan"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan cepat • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan perlahan • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klik butang anak panah untuk memulakan tutorial umum"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
     <string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ketepikan"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan apl terbaharu"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaharu"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 620124e..dddc2fa 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"အလုပ်ပရိုဖိုင်တွင် ဖန်သားပြင်ဓာတ်ပုံ သိမ်းနေသည်…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်းပြီးပါပြီ"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"မျက်နှာပြင်ပုံကို သိမ်း၍မရပါ"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ဖန်သားပြင်ဓာတ်ပုံကို မသိမ်းမီ စက်ပစ္စည်းကို လော့ခ်ဖွင့်ထားရမည်"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"မျက်နှာပြင်ပုံကို ထပ်ရိုက်ကြည့်ပါ"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်း၍မရပါ"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"ကာစ်တင်"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"အမည်မတပ် ကိရိယာ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ကိရိယာများ မရှိ"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ချိတ်ဆက်ထားခြင်းမရှိပါ"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အမြန်အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
     <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="700342473477865107">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် မြားခလုတ်ကို နှိပ်ပါ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ဖန်သားပြင်ကို စကရင်ပွားရန်"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ပယ်ရန်"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"မိုက်ခရိုဖုန်းနှင့် ကင်မရာ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"လတ်တလော အက်ပ်အသုံးပြုမှု"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"လတ်တလောအသုံးပြုမှုကို ကြည့်ရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 722cc12..ff32177 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Lagrer skjermdumpen i jobbprofilen …"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Skjermdumpen er lagret"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Kunne ikke lagre skjermdump"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheten må være låst opp før skjermdumpen kan lagres"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv å ta skjermdump på nytt"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan ikke lagre skjermdumpen"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhet uten navn"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ingen enheter er tilgjengelige"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi er ikke tilkoblet"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader raskt • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader sakte • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klikk på pilen for å starte fellesveiledningen"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Lukk"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nylig appbruk"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se nylig tilgang"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 74e5c6f..61e52bd 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलमा स्क्रिनसट सेभ गरिँदै छ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सेभ गरियो"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"डिभाइस अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रिनसट फेरि लिएर हेर्नुहोस्"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"प्रसारण गर्दै"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"बेनाम उपकरण"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कुनै उपकरणहरू उपलब्ध छैन"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi कनेक्ट गरिएको छैन"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"कलर करेक्सन"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • छिटो चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • बिस्तारै चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
     <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="700342473477865107">"कम्युनल ट्युटोरियल सुरु गर्न एरो बटनमा क्लिक गर्नुहोस्"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर गर्नुहोस्"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"खारेज गर्नुहोस्"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफोन तथा क्यामेरा"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"एपको हालसालैको प्रयोग"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हालसालै एक्सेस गर्ने एप हेर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 6d97a9f..7d21874 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot opslaan in werkprofiel…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot opgeslagen"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Kan screenshot niet opslaan"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Je moet het apparaat ontgrendelen voordat het screenshot kan worden opgeslagen"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer opnieuw een screenshot te maken"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan screenshot niet opslaan"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Casten"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Naamloos apparaat"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen apparaten beschikbaar"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi niet verbonden"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Snel opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Langzaam opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klik op de pijl om de communitytutorial te starten"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
     <string name="install_app" msgid="5066668100199613936">"App installeren"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Sluiten"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfoon en camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app-gebruik"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Recente toegang bekijken"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index a6e14f9..38039cb 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ସ୍କ୍ରିନସଟ ସେଭ କରାଯାଉଛି…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ ହୋଇଛି"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ସ୍କ୍ରୀନ୍‍ଶଟ୍ ସେଭ୍ କରିହେବ ନାହିଁ"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ସ୍କ୍ରିନସଟ୍ ସେଭ୍ କରିବା ପୂର୍ବରୁ ଡିଭାଇସକୁ ଅନଲକ୍ କରାଯିବା ଆବଶ୍ୟକ"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ପୁଣିଥରେ ସ୍କ୍ରୀନ୍‌ଶଟ୍ ନେବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ସ୍କ୍ରିନସଟକୁ ସେଭ୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"କାଷ୍ଟିଙ୍ଗ"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ନାମହୀନ ଡିଭାଇସ୍‍"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"କୌଣସି ଡିଭାଇସ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ୱାଇ-ଫାଇ ସଂଯୋଜିତ ହୋଇନାହିଁ"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"କଲର ଇନଭର୍ସନ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
     <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="700342473477865107">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ତୀର ବଟନରେ କ୍ଲିକ କରନ୍ତୁ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍‍ ବଦଳାନ୍ତୁ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍‌ ଓ ଡାଟା ଡିଲିଟ୍‌ ହୋଇଯିବ।"</string>
@@ -660,7 +664,7 @@
     <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"ବର୍ତ୍ତମାନର"</string>
     <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ଫେରନ୍ତୁ"</string>
     <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ବିଜ୍ଞପ୍ତି"</string>
-    <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀ\'ବୋର୍ଡ ସର୍ଟକଟ୍"</string>
+    <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
     <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"କୀ\'ବୋର୍ଡ୍‍ର ଲେଆଉଟ୍‍କୁ ବଦଳାନ୍ତୁ"</string>
     <string name="keyboard_shortcut_clear_text" msgid="4679927133259287577">"ଟେକ୍ସଟ ଖାଲି କରନ୍ତୁ"</string>
     <string name="keyboard_shortcut_search_list_title" msgid="1156178106617830429">"ସର୍ଟକଟଗୁଡ଼ିକ"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ଡିସପ୍ଲେ ମିରର କରନ୍ତୁ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ମାଇକ୍ରୋଫୋନ ଏବଂ କେମେରା"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ବର୍ତ୍ତମାନର ଆପ ବ୍ୟବହାର"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ବର୍ତ୍ତମାନର ଆକ୍ସେସ ଦେଖନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index faca7b1..9011855 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਕੀਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਦੁਬਾਰਾ ਲੈ ਕੇ ਦੇਖੋ"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"ਕਾਸਟਿੰਗ"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ਬਿਨਾਂ ਨਾਮ ਦਾ ਡੀਵਾਈਸ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ਕੋਈ ਡਿਵਾਈਸਾਂ ਉਪਲਬਧ ਨਹੀਂ"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ਵਾਈ-ਫਾਈ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
     <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="700342473477865107">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਤੀਰ ਬਟਨ \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰੋ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ਖਾਰਜ ਕਰੋ"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਕੈਮਰਾ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤੀ ਗਈ ਐਪ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ਹਾਲੀਆ ਪਹੁੰਚ ਦੇਖੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 3bb539a..9b2b3cf 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Zapisuję zrzut ekranu w profilu służbowym…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Zrzut ekranu został zapisany"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Nie udało się zapisać zrzutu ekranu"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Przed zapisaniem zrzutu ekranu musisz odblokować urządzenie"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Spróbuj jeszcze raz wykonać zrzut ekranu"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nie można zapisać zrzutu ekranu."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Przesyłam"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Urządzenie bez nazwy"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Brak dostępnych urządzeń"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Brak połączenia z Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Szybkie ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wolne ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Kliknij przycisk strzałki, aby uruchomić samouczek społecznościowy"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Powielić na wyświetlaczu zewnętrznym?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Zamknij"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i aparat"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikacje korzystające w ostatnim czasie"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobacz ostatni dostęp"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 8f93b73..a2b1fdb 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 02ee098..373cafb 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"A guardar captura de ecrã no perfil de trabalho…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de ecrã guardada"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Não foi possível guardar a captura de ecrã"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"É necessário desbloquear o dispositivo para guardar a captura de ecrã"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Experimente voltar a efetuar a captura de ecrã."</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não é possível guardar a captura de ecrã."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Transmissão"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Sem dispositivos disponíveis"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não ligado"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção da cor"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar rapidamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar lentamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Clique no botão de seta para iniciar o tutorial coletivo"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilização recente da app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acesso recente"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 8f93b73..a2b1fdb 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 14cdeac..1aabc3e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Se salvează captura în profilul de serviciu…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pentru a salva captura de ecran, trebuie să deblochezi dispozitivul"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încearcă să faci din nou o captură de ecran"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nu se poate salva captura de ecran"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Se proiectează"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispozitiv nedenumit"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Niciun dispozitiv disponibil"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Rețeaua Wi-Fi nu este conectată"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <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="700342473477865107">"Dă clic pe butonul săgeată pentru a începe tutorialul pentru comunitate"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Oglindești pe ecranul extern?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Afișare în oglindă"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Închide"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfon și cameră"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilizare recentă în aplicații"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vezi accesarea recentă"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index c5379c2..35b4c29 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Сохранение скриншота в рабочем профиле…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сохранен"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Не удалось сохранить скриншот"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Чтобы сохранить скриншот, разблокируйте устройство."</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Попробуйте сделать скриншот снова."</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не удалось сохранить скриншот."</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Передача изображения"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Безымянное устройство"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нет доступных устройств"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нет подключения к сети Wi-Fi."</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Быстрая зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Медленная зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Нажмите кнопку со стрелкой, чтобы ознакомиться с руководством"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Дублировать дисплей"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыть"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавнее использование приложениями"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Посмотреть недавний доступ"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index d53f0eb..f2dd421 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"කාර්යාල පැතිකඩ වෙත තිර රුව සුරකිමින්…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"තිර රුව සුරකින ලදී"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"තිර රුව සුරැකිය නොහැකි විය"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"තිර රුව සුරැකීමට පෙර උපාංගය අගුලු හැරිය යුතුය"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"තිර රුව නැවත ගැනීමට උත්සාහ කරන්න"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"තිර රුව සුරැකීමට නොහැකිය"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"කාස්ට් කිරීම"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"නම් නොකළ උපාංගය"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"උපාංග නොතිබේ"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi සම්බන්ධ නොවීය"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • වේගයෙන් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • සෙමින් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
     <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="700342473477865107">"වාර්ගික නිබන්ධනය ආරම්භ කිරීමට ඊතල බොත්තම ක්ලික් කරන්න"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"සංදර්ශකය දර්පණය කරන්න"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"අස් කරන්න"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"මයික්‍රොෆෝනය සහ කැමරාව"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"මෑත යෙදුම් භාවිතය"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"මෑත ප්‍රවේශය බලන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 7fa0d07..844f30c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukladá sa snímka obrazovky do pracovného profilu…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Snímka obrazovky bola uložená"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Snímku obrazovky sa nepodarilo uložiť"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred uložením snímky obrazovky je potrebné zariadenie odomknúť"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skúste snímku urobiť znova"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímka obrazovky sa nedá uložiť"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Prenáša sa"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepomenované zariadenie"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nie sú k dispozícii žiadne zariadenia"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Sieť Wi‑Fi nie je pripojená"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzia farieb"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Úprava farieb"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa rýchlo • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa pomaly • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Ak chcete spustiť komunitný návod, kliknite na tlačidlo so šípkou"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliť na externú obrazovku?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Zavrieť"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofón a fotoaparát"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávne využitie aplikácie"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobraziť nedávny prístup"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 87c18f4..1eab754 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Shranjevanje posnetka zaslona v delovni profil …"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Posnetek zaslona je shranjen"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Posnetka zaslona ni bilo mogoče shraniti"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred shranjevanjem posnetka zaslona morate odkleniti napravo"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Poskusite znova ustvariti posnetek zaslona"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Posnetka zaslona ni mogoče shraniti"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Predvajanje"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovana naprava"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Na voljo ni nobene naprave"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Povezava Wi-Fi ni vzpostavljena"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svetlost"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija barv"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Popravljanje barv"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hitro polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Počasno polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Kliknite gumb s puščico, da zaženete vadnico za skupnost"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
     <string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite zrcaliti v zunanji zaslon?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Opusti"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon in fotoaparat"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna uporaba v aplikacijah"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ogled nedavnih dostopov"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 3e1471a..cafd6a0 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pamja e ekranit po ruhet te profili i punës…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Pamja e ekranit u ruajt"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Pamja e ekranit nuk mund të ruhej"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pajisja duhet të shkyçet para se të mund të ruhet pamja e ekranit"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Provo ta nxjerrësh përsëri pamjen e ekranit"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Pamja e ekranit nuk mund të ruhet"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Po transmeton"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Pajisje e paemërtuar"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nuk ofrohet për përdorim asnjë pajisje"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nuk është lidhur"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet shpejt • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet ngadalë • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Kliko mbi butonin e shigjetës për të filluar udhëzuesin e përbashkët"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
     <string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Hiq"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni dhe kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Përdorimi i fundit i aplikacionit"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Shiko qasjen e fundit"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 07897d7..8c1450a1 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Снимак екрана се чува на пословном профилу…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Снимак екрана је сачуван"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Чување снимка екрана није успело"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уређај мора да буде откључан да би снимак екрана могао да се сачува"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Пробајте да поново направите снимак екрана"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Чување снимка екрана није успело"</string>
@@ -101,8 +103,8 @@
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
     <string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Желите да започнете снимање?"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
-    <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+    <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
     <string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Започни снимање"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Снимај звук"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук уређаја"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Пребацивање"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименовани уређај"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Није доступан ниједан уређај"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Брзо се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Споро се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
     <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="700342473477865107">"Кликните на дугме са стрелицом да бисте започели заједнички водич"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -418,17 +422,17 @@
     <string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Једна апликација"</string>
     <string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Делите или снимите апликацију"</string>
     <string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Желите да почнете снимање или пребацивање помоћу апликације <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
-    <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+    <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+    <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
     <string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Покрени"</string>
     <string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је онемогућила ову опцију"</string>
     <string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Желите да започнете пребацивање?"</string>
-    <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
-    <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+    <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+    <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
     <string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Започни пребацивање"</string>
     <string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Желите да почнете да делите?"</string>
-    <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
-    <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+    <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+    <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
     <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Дељење се зауставља када мењате апликације"</string>
     <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Дели ову апликацију"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Одбаци"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавно користила апликација"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Прикажи недавни приступ"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index aee168e..59bb1f5 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sparar skärmbild i jobbprofilen …"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Skärmbilden har sparats"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Det gick inte att spara skärmbilden"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skärmbilden kan bara sparas om enheten är upplåst"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Testa att ta en skärmbild igen"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Det gick inte att spara skärmbilden"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Castar"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Namnlös enhet"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Inga tillgängliga enheter"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ej ansluten till wifi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas snabbt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas långsamt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Klicka på pilknappen för att börja med gruppguiden"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
     <string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorera"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon och kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Senaste appanvändning"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se senaste åtkomst"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index be3badd..b977dc4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Inahifadhi picha ya skrini kwenye wasifu wa kazini…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Imehifadhi picha ya skrini"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Imeshindwa kuhifadhi picha ya skrini"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ni sharti ufungue kifaa kabla ya kuhifadhi picha ya skrini"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Jaribu kupiga picha ya skrini tena"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Imeshindwa kuhifadhi picha ya skrini"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Inatuma"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Kifaa hakina jina"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Hakuna vifaa vilivyopatikana"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi haijaunganishwa"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji kwa kasi • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji polepole • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Bofya kwenye kitufe cha kishale ili kuanzisha mafunzo ya jumuiya"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
     <string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ondoa"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Maikrofoni na Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Matumizi ya programu hivi majuzi"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Angalia ufikiaji wa majuzi"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ea3c012..1f671ac 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -37,6 +37,9 @@
 
     <bool name="config_use_large_screen_shade_header">true</bool>
 
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
+
     <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
     string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
     separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 785c4d5..3d05824 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"பணிக் கணக்கில் ஸ்கிரீன்ஷாட் சேமிக்கப்படுகிறது…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"ஸ்கிரீன்ஷாட் சேமிக்கப்பட்டது"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ஸ்கிரீன் ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ஸ்கிரீன்ஷாட் சேமிக்கப்படுவதற்கு முன்பு சாதனம் அன்லாக் செய்யப்பட வேண்டும்"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ஸ்கிரீன் ஷாட்டை மீண்டும் எடுக்க முயலவும்"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ஸ்கிரீன்ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"அனுப்புகிறது"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"பெயரிடப்படாத சாதனம்"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"சாதனங்கள் இல்லை"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"வைஃபை இணைக்கப்படவில்லை"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்‌ஷன்"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • வேகமாகச் சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • மெதுவாக சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
     <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="700342473477865107">"சமூகப் பயிற்சியைத் தொடங்க அம்புக்குறி பட்டனைக் கிளிக் செய்யுங்கள்"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"டிஸ்பிளேயை மிரர் செய்"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"வேண்டாம்"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"மைக்ரோஃபோனும் கேமராவும்"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"சமீபத்திய ஆப்ஸ் பயன்பாடு"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"சமீபத்திய அணுகலைக் காட்டு"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 96d77b7..6d68b63 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"స్క్రీన్‌షాట్‌ను వర్క్ ప్రొఫైల్‌కు సేవ్ చేస్తోంది…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్‌షాట్ సేవ్ చేయబడింది"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"స్క్రీన్‌షాట్‌ని సేవ్ చేయడం సాధ్యం కాలేదు"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"స్క్రీన్‌షాట్ సేవ్ అవ్వకముందే పరికరం అన్‌లాక్ చేయబడాలి"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"స్క్రీన్‌షాట్ తీయడానికి మళ్లీ ట్రై చేయండి"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"స్క్రీన్‌షాట్‌ను సేవ్ చేయడం సాధ్యపడలేదు"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"ప్రసారం చేస్తోంది"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"పేరులేని పరికరం"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"పరికరాలు ఏవీ అందుబాటులో లేవు"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi కనెక్ట్ కాలేదు"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • వేగంగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
     <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="700342473477865107">"కమ్యూనల్ ట్యుటోరియల్‌ను ప్రారంభించడానికి బాణం బటన్‌పై క్లిక్ చేయండి"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్‌డౌన్ మెనూ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్‌లోని అన్ని యాప్‌లు మరియు డేటా తొలగించబడతాయి."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"మిర్రర్ డిస్‌ప్లే"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"విస్మరించండి"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"మైక్రోఫోన్ &amp; కెమెరా"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ఇటీవలి యాప్ వినియోగం"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ఇటీవలి యాక్సెస్‌ను చూడండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f8fbfbe..3f5a47e 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"กำลังบันทึกภาพหน้าจอไปยังโปรไฟล์งาน…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"บันทึกภาพหน้าจอแล้ว"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"บันทึกภาพหน้าจอไม่ได้"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ต้องปลดล็อกอุปกรณ์ก่อนจึงจะบันทึกภาพหน้าจอได้"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ลองบันทึกภาพหน้าจออีกครั้ง"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"บันทึกภาพหน้าจอไม่ได้"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"กำลังส่ง"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"อุปกรณ์ที่ไม่มีชื่อ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ไม่มีอุปกรณ์ที่สามารถใช้ได้"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างเร็ว • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างช้าๆ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"คลิกปุ่มลูกศรเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"มิเรอร์จอแสดงผล"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ปิด"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ไมโครโฟนและกล้อง"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"การใช้แอปครั้งล่าสุด"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ดูการเข้าถึงล่าสุด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 435398d..3fac637 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sine-save ang screenshot sa profile sa trabaho…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Na-save ang screenshot"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Hindi ma-save ang screenshot"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Dapat naka-unlock ang device bago ma-save ang screenshot"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Subukang kumuhang muli ng screenshot"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Hindi ma-save ang screenshot"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Nagka-cast"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Walang pangalang device"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Walang available na mga device"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Hindi nakakonekta sa Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabilis na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabagal na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
     <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="700342473477865107">"I-click ang arrow button para simulan ang communal na tutorial"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
     <string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"I-dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikropono at Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kamakailang paggamit ng app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Tingnan ang kamakailang access"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index d0ddbec..ca357b41 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekran görüntüsü iş profiline kaydediliyor…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekran görüntüsü kaydedildi"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekran görüntüsü kaydedilemedi"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ekran görüntüsünün kaydedilebilmesi için cihazın kilidi açık olmalıdır"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tekrar ekran görüntüsü almayı deneyin"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekran görüntüsü kaydedilemiyor"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Yayınlanıyor"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Kullanılabilir cihaz yok"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Kablosuz ağ bağlı değil"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hızlı şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Yavaş şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
     <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="700342473477865107">"Ortak eğitimi başlatmak için ok düğmesini tıklayın"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Kapat"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ve Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son uygulama kullanımı"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son erişimi göster"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 1489dc7..6078e31 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Зберігання знімка екрана в робочому профілі…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Знімок екрана збережено"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Не вдалося зберегти знімок екрана"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Щоб зберегти знімок екрана, розблокуйте пристрій"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Спробуйте зробити знімок екрана ще раз"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не вдалося зберегти знімок екрана"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Трансляція"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Пристрій без назви"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Немає пристроїв"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не під’єднано"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Швидке заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Повільне заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
     <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="700342473477865107">"Натисніть кнопку зі стрілкою, щоб відкрити спільний навчальний посібник"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Дублювати екран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Закрити"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрофон і камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нещодавнє використання додатками"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Переглянути нещодавній доступ"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 9d69a5e..5cf763f 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"اسکرین شاٹ دفتری پروفائل میں محفوظ کیا جا رہا ہے…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"اسکرین شاٹ محفوظ ہو گیا"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکا"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"اسکرین شاٹ محفوظ کرنے سے پہلے آلے کو غیر مقفل کرنا ضروری ہے"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوبارہ اسکرین شاٹ لینے کی کوشش کریں"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکتا"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"کاسٹنگ"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"بغیر نام والا آلہ"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"کوئی آلات دستیاب نہیں ہیں"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏Wi-Fi سے منسلک نہیں ہے"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • تیزی سے چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • آہستہ چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
     <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="700342473477865107">"کمیونل ٹیوٹوریل شروع کرنے کے لیے تیر کے نشان والے بٹن پر کلک کریں"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ڈسپلے کو دو طرفہ مطابقت پذیر بنائیں"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"برخاست کریں"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"مائیکروفون اور کیمرا"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"حالیہ ایپ کا استعمال"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"حالیہ رسائی دیکھیں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 31b216b..412dd8a 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -20,8 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4811759950673118541">"Tizim interfeysi"</string>
-    <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash funksiyasi yoqilsinmi?"</string>
-    <string name="battery_low_description" msgid="3282977755476423966">"<xliff:g id="PERCENTAGE">%s</xliff:g> batareya quvvati qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
+    <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash yoqilsinmi?"</string>
+    <string name="battery_low_description" msgid="3282977755476423966">"Batareya quvvati <xliff:g id="PERCENTAGE">%s</xliff:g> qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
     <string name="battery_low_intro" msgid="5148725009653088790">"Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
     <string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> qoldi"</string>
     <string name="invalid_charger_title" msgid="938685362320735167">"USB orqali quvvatlash imkonsiz"</string>
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Skrinshot ish profiliga saqlanmoqda…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinshot saqlandi"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinshot saqlanmadi"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinshotni saqlashdan oldin qurilma qulflanmagan boʻlishi lozim"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Qayta skrinshot olib ko‘ring"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinshot saqlanmadi"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Translatsiya qilinmoqda"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nomsiz qurilma"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Qurilmalar topilmadi"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tarmoqqa ulanmagan"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Yorqinlik"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ranglarni akslantirish"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ranglarni tuzatish"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Tez quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sekin quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
     <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="700342473477865107">"Qoʻllanma bilan tanishish uchun strelka tugmasini bosing"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
     <string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Displeyni akslantirish"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Yopish"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon va kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ilovadan oxirgi foydalanish"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Oxirgi ruxsatni koʻrish"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 814e0a6..45dfc45 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Đang lưu ảnh chụp màn hình vào hồ sơ công việc…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Đã lưu ảnh chụp màn hình"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Không thể lưu ảnh chụp màn hình"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Bạn phải mở khóa thiết bị để chúng tôi có thể lưu ảnh chụp màn hình"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Hãy thử chụp lại màn hình"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Không thể lưu ảnh chụp màn hình"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Đang truyền"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Thiết bị không có tên"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Không có thiết bị nào"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Chưa kết nối với Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc nhanh • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc chậm • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Nhấp vào nút mũi tên để bắt đầu xem hướng dẫn chung"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string>
     <string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Đồng bộ hoá hai chiều sang màn hình ngoài?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Đóng"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrô và máy ảnh"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Hoạt động sử dụng gần đây của ứng dụng"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Xem hoạt động truy cập gần đây"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 8b2caf0..17a66787 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在将屏幕截图保存到工作资料…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"已保存屏幕截图"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"无法保存屏幕截图"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必须先解锁设备,然后才能保存屏幕截图"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"请再次尝试截屏"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"无法保存屏幕截图"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"正在投放"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名设备"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"没有可用设备"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未连接到 WLAN 网络"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在快速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在慢速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
     <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="700342473477865107">"点击箭头按钮,即可启动公共教程"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"镜像显示"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"关闭"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"麦克风和摄像头"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期应用对手机传感器的使用情况"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期使用情况"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 8a160c3..48fbf7c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存至工作設定檔…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕擷取畫面已儲存"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕擷取畫面"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再嘗試拍攝螢幕擷取畫面"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"正在放送"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
     <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="700342473477865107">"按一下箭咀鍵,即可開始共用教學課程"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期應用程式使用情況"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 38b81a0..55d4cb6 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存到工作資料夾…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕截圖已儲存"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕截圖"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再次嘗試拍攝螢幕截圖"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"投放"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
     <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="700342473477865107">"點選箭頭按鈕,即可開始通用教學課程"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string>
@@ -1183,10 +1187,8 @@
     <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 mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近曾使用感應器的應用程式"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index fc70417..b883b9a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -76,6 +76,8 @@
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ilondoloza isithombe-skrini kuphrofayela yomsebenzi…"</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Isithombe-skrini silondoloziwe"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Ayikwazanga ukulondoloza isithombe-skrini"</string>
+    <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+    <skip />
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Idivayisi kufanele ivulwe ngaphambi kokuthi isithombe-skrini singalondolozwa"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zama ukuthatha isithombe-skrini futhi"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ayikwazi ukulondoloza isithombe-skrini"</string>
@@ -280,7 +282,8 @@
     <string name="quick_settings_casting" msgid="1435880708719268055">"Ukusakaza"</string>
     <string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Idivayisi engenalo igama"</string>
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ayikho idivayisi etholakalayo"</string>
-    <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"I-Wi-Fi ayixhunyiwe"</string>
+    <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+    <skip />
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string>
@@ -395,6 +398,7 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja ngokushesha • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja kancane • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <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="700342473477865107">"Chofoza inkinobho yomcibisholo ukuze uqalise isifundo somphakathi"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
@@ -1183,10 +1187,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
     <string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Chitha"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Imakrofoni Nekhamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ukusetshenziswa kwakamuva kwe-app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Bona ukufinyelela kwakamuva"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1add90f..572f6ff 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -531,19 +531,25 @@
     </string>
 
     <!-- A path similar to frameworks/base/core/res/res/values/config.xml
-      config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
-      cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
-      SystemUI will draw this "protection path" instead of the display cutout path that is normally
-      used for anti-aliasing.
+      config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a outer
+      display cutout. If present as well as config_enableDisplayCutoutProtection is set to true,
+      then SystemUI will draw this "protection path" instead of the display cutout path that is
+      normally used for anti-aliasing.
 
       This path will only be drawn when the front-facing camera turns on, otherwise the main
       DisplayCutout path will be rendered
        -->
     <string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
 
-    <!--  ID for the camera that needs extra protection -->
+    <!--  ID for the camera of outer display that needs extra protection -->
     <string translatable="false" name="config_protectedCameraId"></string>
 
+    <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
+    <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
+
+    <!-- ID for the camera of inner display that needs extra protection -->
+    <string translatable="false" name="config_protectedInnerCameraId"></string>
+
     <!-- Comma-separated list of packages to exclude from camera protection e.g.
     "com.android.systemui,com.android.xyz" -->
     <string translatable="false" name="config_cameraProtectionExcludedPackages"></string>
@@ -730,6 +736,9 @@
     <!-- Whether the communal service should be enabled -->
     <bool name="config_communalServiceEnabled">false</bool>
 
+    <!-- Component names of allowed communal widgets -->
+    <string-array name="config_communalWidgetAllowlist" translatable="false" />
+
     <!-- Component name of communal source service -->
     <string name="config_communalSourceComponent" translatable="false">@null</string>
 
@@ -947,6 +956,9 @@
     <!-- Flag controlling whether visual query attention detection has been enabled. -->
     <bool name="config_enableVisualQueryAttentionDetection">false</bool>
 
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
+
     <!--
     Whether the scene container framework is enabled.
 
@@ -954,4 +966,15 @@
     bouncer, lockscreen, shade, and quick settings.
     -->
     <bool name="config_sceneContainerFrameworkEnabled">true</bool>
+
+    <!--
+    Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
+    TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
+    -->
+    <integer name="config_restToUnlockDuration">300</integer>
+
+    <!--
+    Width in pixels of the Side FPS sensor.
+    -->
+    <integer name="config_sfpsSensorWidth">200</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0ee5da2..10fd8b5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -177,6 +177,12 @@
     <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
         match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
     <dimen name="status_bar_mobile_type_size">16sp</dimen>
+    <!-- Size of the view that contains the network type. Should be equal to
+    status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding -->
+    <dimen name="status_bar_mobile_container_height">18sp</dimen>
+    <!-- Corner radius for the background of the network type indicator. Should be equal to
+        status_bar_mobile_container_height / 2 -->
+    <dimen name="status_bar_mobile_container_corner_radius">9sp</dimen>
     <!-- Size of the view displaying the mobile roam icon in the status bar. This value should
         match the viewport size of drawable stat_sys_roaming -->
     <dimen name="status_bar_mobile_roam_size">8sp</dimen>
@@ -760,6 +766,8 @@
     <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+    <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
+    <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
 
     <dimen name="notification_scrim_corner_radius">32dp</dimen>
 
@@ -846,12 +854,17 @@
     <!-- Amount the button should shake when it's not long-pressed for long enough. -->
     <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
 
-    <dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
+    <dimen name="keyguard_affordance_horizontal_offset">16dp</dimen>
     <dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
     <!-- Value should be at least sum of 'keyguard_affordance_width' +
          'keyguard_affordance_horizontal_offset' -->
     <dimen name="keyguard_indication_area_padding">82dp</dimen>
 
+    <!-- The width/padding of the communal tutorial indicator on keyguard. -->
+    <dimen name="communal_tutorial_indicator_fixed_width">168dp</dimen>
+    <dimen name="communal_tutorial_indicator_padding">24dp</dimen>
+    <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</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>
@@ -1350,6 +1363,9 @@
     <dimen name="screenrecord_options_padding_bottom">16dp</dimen>
     <dimen name="screenrecord_buttons_margin_top">20dp</dimen>
 
+    <!-- Connected display dialog -->
+    <dimen name="connected_display_dialog_logo_size">48dp</dimen>
+
     <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 
@@ -1653,6 +1669,15 @@
     <!-- Height percentage of the parent container occupied by the communal view -->
     <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
 
+    <!-- Size of each communal grid column -->
+    <dimen name="communal_grid_column_size">64dp</dimen>
+    <!-- Size of each communal grid gutter between columns -->
+    <dimen name="communal_grid_gutter_size">16dp</dimen>
+    <!-- Height of the communal grid layout -->
+    <dimen name="communal_grid_height">630dp</dimen>
+    <!-- Number of columns for each communal card -->
+    <integer name="communal_grid_columns_per_card">6</integer>
+
     <dimen name="drag_and_drop_icon_size">70dp</dimen>
 
     <dimen name="qs_tile_service_request_dialog_width">304dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 81101d8..74d435d 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -222,12 +222,14 @@
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
+    <item type="id" name="communal_tutorial_indicator" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
     <item type="id" name="privacy_dialog_manage_app_button" />
 
     <!-- Communal mode -->
+    <item type="id" name="communal_hub" />
     <item type="id" name="communal_widget_wrapper" />
 
     <!-- Values assigned to the views in Biometrics Prompt -->
@@ -246,4 +248,14 @@
 
     <!-- Tag set on the Compose implementation of the QS footer actions. -->
     <item type="id" name="tag_compose_qs_footer_actions" />
+
+    <!--
+    Ids for the device entry icon.
+        device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg
+        device_entry_icon_fg: fp/lock/unlock icon
+        device_entry_icon_bg: background protection behind the icon
+    -->
+    <item type="id" name="device_entry_icon_view" />
+    <item type="id" name="device_entry_icon_fg" />
+    <item type="id" name="device_entry_icon_bg" />
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a2637d5..4c41ca4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -209,6 +209,8 @@
     <string name="screenshot_saved_title">Screenshot saved</string>
     <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
     <string name="screenshot_failed_title">Couldn\'t save screenshot</string>
+    <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] -->
+    <string name="screenshot_failed_external_display_indication">External Display</string>
     <!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
     <string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
     <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
@@ -627,7 +629,7 @@
     <!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
     <!-- QuickSettings: Bluetooth dialog subtitle [CHAR LIMIT=NONE]-->
-    <string name="quick_settings_bluetooth_tile_subtitle">Tap a device to connect</string>
+    <string name="quick_settings_bluetooth_tile_subtitle">Tap to connect or disconnect a device </string>
     <!-- QuickSettings: Bluetooth dialog pair new devices [CHAR LIMIT=NONE]-->
     <string name="pair_new_bluetooth_devices">Pair new device</string>
     <!-- QuickSettings: Bluetooth dialog see all devices [CHAR LIMIT=NONE]-->
@@ -711,8 +713,8 @@
      <!-- QuickSettings: Cast detail panel, default device description [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] -->
     <string name="quick_settings_cast_detail_empty_text">No devices available</string>
-    <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string>
+    <!-- QuickSettings: Cast unavailable, text when not connected to WiFi or ethernet[CHAR LIMIT=NONE] -->
+    <string name="quick_settings_cast_no_network">No Wi\u2011Fi or Ethernet connection</string>
     <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_dialog_title">Brightness</string>
     <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
@@ -1043,6 +1045,9 @@
     <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
     <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
+    <!-- Indicator shown to start the communal tutorial. [CHAR LIMIT=100] -->
+    <string name="communal_tutorial_indicator_text">Click on the arrow button to start the communal tutorial</string>
+
     <!-- Related to user switcher --><skip/>
 
     <!-- Accessibility label for the button that opens the user switcher. -->
@@ -3017,8 +3022,6 @@
     <string name="dream_overlay_status_bar_mic_off">Mic is off</string>
     <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
     <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
-    <!-- Content description for the assistant attention indicator [CHAR LIMIT=NONE] -->
-    <string name="dream_overlay_status_bar_assistant_attention_indicator">Assistant is listening</string>
     <!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
     <string name="dream_overlay_status_bar_notification_indicator">{count, plural,
     =1 {# notification}
@@ -3204,7 +3207,7 @@
     <string name="priority_mode_dream_overlay_content_description">Priority mode on</string>
 
     <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] -->
-    <string name="assistant_attention_content_description">Assistant attention on</string>
+    <string name="assistant_attention_content_description">User presence is detected</string>
 
     <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
     <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
@@ -3219,6 +3222,9 @@
     <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
     <string name="dismiss_dialog">Dismiss</string>
 
+    <!--- Content description of the connected display status bar icon that appears every time a display is connected [CHAR LIMIT=NONE]-->
+    <string name="connected_display_icon_desc">Display connected</string>
+
     <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
     <string name="privacy_dialog_title">Microphone &amp; Camera</string>
     <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4b430e1
--- /dev/null
+++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index db46ccf..80f70a0 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -33,6 +33,10 @@
     val hasFingerprint: Boolean
         get() = fingerprintProperties != null
 
+    /** If SFPS authentication is available. */
+    val hasSfps: Boolean
+        get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
+
     /** If fingerprint authentication is available (and [faceProperties] is non-null). */
     val hasFace: Boolean
         get() = faceProperties != null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
rename to packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
index 0aa6b0b..9b7cd70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.dagger.qualifiers
 
-package com.android.systemui.qs.tiles.base.interactor
+import javax.inject.Qualifier
 
-data class QSTileDataRequest(
-    val userId: Int,
-    val trigger: StateUpdateTrigger,
-)
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Tracing
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 1e89614..400f652 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -702,13 +702,18 @@
 
         @Override
         public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
-            // Only hide the icon if the top task changes its requestedOrientation
-            // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
-            Optional.ofNullable(ActivityManagerWrapper.getInstance())
-                    .map(ActivityManagerWrapper::getRunningTask)
-                    .ifPresent(a -> {
-                        if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
-                    });
+            mBgExecutor.execute(() -> {
+                // Only hide the icon if the top task changes its requestedOrientation Launcher can
+                // alter its requestedOrientation while it's not on top, don't hide on this
+                Optional.ofNullable(ActivityManagerWrapper.getInstance())
+                        .map(ActivityManagerWrapper::getRunningTask)
+                        .ifPresent(a -> {
+                            if (a.id == taskId) {
+                                mMainThreadHandler.post(() ->
+                                        setRotateSuggestionButtonState(false /* visible */));
+                            }
+                        });
+            });
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 80040a3..631423e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -155,6 +155,26 @@
         }
     }
 
+
+    /**
+     * Requests for a new snapshot to be taken for the given task, stores it in the cache, and
+     * returns a {@link ThumbnailData} with the result.
+     */
+    @NonNull
+    public ThumbnailData takeTaskThumbnail(int taskId) {
+        TaskSnapshot snapshot = null;
+        try {
+            snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to take task snapshot", e);
+        }
+        if (snapshot != null) {
+            return new ThumbnailData(snapshot);
+        } else {
+            return new ThumbnailData();
+        }
+    }
+
     /**
      * Removes the outdated snapshot of home task.
      *
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index eb20669..c505bd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT =
             InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
+            InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -77,6 +79,7 @@
             CUJ_CLOSE_ALL_APPS_SWIPE,
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 1e7222d..88b9c02 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -233,6 +233,11 @@
                 runner.onAnimationCancelled();
                 finishRunnable.run();
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean aborted)
+                    throws RemoteException {
+            }
         };
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt
new file mode 100644
index 0000000..f219cec
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.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.unfold.system
+
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/** Provides whether the device is folded. */
+interface DeviceStateRepository {
+    val isFolded: Flow<Boolean>
+}
+
+@Singleton
+class DeviceStateRepositoryImpl
+@Inject
+constructor(
+    private val foldProvider: FoldProvider,
+    @UnfoldMain private val executor: Executor,
+) : DeviceStateRepository {
+
+    override val isFolded: Flow<Boolean>
+        get() =
+            callbackFlow {
+                    val callback = FoldCallback { isFolded -> trySend(isFolded) }
+                    foldProvider.registerCallback(callback, executor)
+                    awaitClose { foldProvider.unregisterCallback(callback) }
+                }
+                .buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index fe607e1..7b67e3f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -48,6 +48,9 @@
     abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
 
     @Binds
+    abstract fun deviceStateRepository(provider: DeviceStateRepositoryImpl): DeviceStateRepository
+
+    @Binds
     @UnfoldMain
     abstract fun mainExecutor(@Main executor: Executor): Executor
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt
new file mode 100644
index 0000000..63ea116
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.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.util
+
+import android.os.Trace
+
+/**
+ * Utility class used to log state changes easily in a track with a custom name.
+ *
+ * Example of usage:
+ * ```kotlin
+ * class MyClass {
+ *    val screenStateLogger = TraceStateLogger("Screen state")
+ *
+ *    fun onTurnedOn() { screenStateLogger.log("on") }
+ *    fun onTurnedOff() { screenStateLogger.log("off") }
+ * }
+ * ```
+ *
+ * This creates a new slice in a perfetto trace only if the state is different than the previous
+ * one.
+ */
+class TraceStateLogger(
+    private val trackName: String,
+    private val logOnlyIfDifferent: Boolean = true,
+    private val instantEvent: Boolean = true
+) {
+
+    private var previousValue: String? = null
+
+    /** If needed, logs the value to a track with name [trackName]. */
+    fun log(newValue: String) {
+        if (instantEvent) {
+            Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, newValue)
+        }
+        if (logOnlyIfDifferent && previousValue == newValue) return
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, 0)
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, newValue, 0)
+        previousValue = newValue
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 7b2e1af..e459034 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,13 +18,25 @@
 
 import android.os.Trace
 import android.os.TraceNameSupplier
+import android.util.Log
+import com.android.systemui.util.tracing.TraceContextElement
+import com.android.systemui.util.tracing.TraceData
+import com.android.systemui.util.tracing.TraceData.Companion.FIRST_VALID_SPAN
+import com.android.systemui.util.tracing.TraceData.Companion.INVALID_SPAN
+import com.android.systemui.util.tracing.threadLocalTrace
 import java.util.concurrent.atomic.AtomicInteger
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.random.Random
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 
 /**
  * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
@@ -44,6 +56,10 @@
 
 class TraceUtils {
     companion object {
+        const val TAG = "TraceUtils"
+        private const val DEBUG_COROUTINE_TRACING = false
+        const val DEFAULT_TRACK_NAME = "AsyncTraces"
+
         inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
             return Runnable { traceSection(tag) { block() } }
         }
@@ -73,7 +89,7 @@
          * under a single track.
          */
         inline fun <T> traceAsync(method: String, block: () -> T): T =
-            traceAsync("AsyncTraces", method, block)
+            traceAsync(DEFAULT_TRACK_NAME, method, block)
 
         /**
          * Creates an async slice in a track with [trackName] while [block] runs.
@@ -93,16 +109,313 @@
         }
 
         /**
-         * Convenience method to avoid one indentation level when we want to add a trace when
-         * launching a coroutine
+         * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+         * tracing.
+         *
+         * @see traceCoroutine
          */
-        fun <T> CoroutineScope.tracedAsync(
-            method: String,
+        inline fun CoroutineScope.launch(
+            crossinline spanName: () -> String,
             context: CoroutineContext = EmptyCoroutineContext,
-            start: CoroutineStart = CoroutineStart.DEFAULT,
-            block: suspend () -> T
-        ): Deferred<T> {
-            return async(context, start) { traceAsync(method) { block() } }
+            // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+            crossinline block: suspend CoroutineScope.() -> Unit
+        ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+         * tracing.
+         *
+         * @see traceCoroutine
+         */
+        inline fun CoroutineScope.launch(
+            spanName: String,
+            context: CoroutineContext = EmptyCoroutineContext,
+            // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+            crossinline block: suspend CoroutineScope.() -> Unit
+        ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+         * tracing
+         *
+         * @see traceCoroutine
+         */
+        inline fun <T> CoroutineScope.async(
+            crossinline spanName: () -> String,
+            context: CoroutineContext = EmptyCoroutineContext,
+            // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+            crossinline block: suspend CoroutineScope.() -> T
+        ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+         * tracing.
+         *
+         * @see traceCoroutine
+         */
+        inline fun <T> CoroutineScope.async(
+            spanName: String,
+            context: CoroutineContext = EmptyCoroutineContext,
+            // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+            crossinline block: suspend CoroutineScope.() -> T
+        ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+         *
+         * @see traceCoroutine
+         */
+        inline fun <T> runBlocking(
+            crossinline spanName: () -> String,
+            context: CoroutineContext,
+            crossinline block: suspend () -> T
+        ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+         *
+         * @see traceCoroutine
+         */
+        inline fun <T> runBlocking(
+            spanName: String,
+            context: CoroutineContext,
+            crossinline block: suspend CoroutineScope.() -> T
+        ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+         *
+         * @see traceCoroutine
+         */
+        suspend inline fun <T> withContext(
+            spanName: String,
+            context: CoroutineContext,
+            crossinline block: suspend CoroutineScope.() -> T
+        ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+         *
+         * @see traceCoroutine
+         */
+        suspend inline fun <T> withContext(
+            crossinline spanName: () -> String,
+            context: CoroutineContext,
+            crossinline block: suspend CoroutineScope.() -> T
+        ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+        /**
+         * A hacky way to propagate the value of the COROUTINE_TRACING flag for static usage in this
+         * file. It should only every be set to true during startup. Once true, it cannot be set to
+         * false again.
+         */
+        var coroutineTracingIsEnabled = false
+            set(v) {
+                if (v) field = true
+            }
+
+        /**
+         * Traces a section of work of a `suspend` [block]. The trace sections will appear on the
+         * thread that is currently executing the [block] of work. If the [block] is suspended, all
+         * trace sections added using this API will end until the [block] is resumed, which could
+         * happen either on this thread or on another thread. If a child coroutine is started, it
+         * will inherit the trace sections of its parent. The child will continue to print these
+         * trace sections whether or not the parent coroutine is still running them.
+         *
+         * The current [CoroutineContext] must have a [TraceContextElement] for this API to work.
+         * Otherwise, the trace sections will be dropped.
+         *
+         * For example, in the following trace, Thread #1 ran some work, suspended, then continued
+         * working on Thread #2. Meanwhile, Thread #2 created a new child coroutine which inherited
+         * its trace sections. Then, the original coroutine resumed on Thread #1 before ending.
+         * Meanwhile Thread #3 is still printing trace sections from its parent because they were
+         * copied when it was created. There is no way for the parent to communicate to the child
+         * that it marked these slices as completed. While this might seem counterintuitive, it
+         * allows us to pinpoint the origin of the child coroutine's work.
+         *
+         * ```
+         * Thread #1 | [==== Slice A ====]                        [==== Slice A ====]
+         *           |       [==== B ====]                        [=== B ===]
+         * --------------------------------------------------------------------------------------
+         * Thread #2 |                    [====== Slice A ======]
+         *           |                    [========= B =========]
+         *           |                        [===== C ======]
+         * --------------------------------------------------------------------------------------
+         * Thread #3 |                            [== Slice A ==]                [== Slice A ==]
+         *           |                            [===== B =====]                [===== B =====]
+         *           |                            [===== C =====]                [===== C =====]
+         *           |                                                               [=== D ===]
+         * ```
+         *
+         * @param name The name of the code section to appear in the trace
+         * @see endSlice
+         * @see traceCoroutine
+         */
+        @OptIn(ExperimentalCoroutinesApi::class)
+        suspend inline fun <T> traceCoroutine(
+            spanName: Lazy<String>,
+            crossinline block: suspend () -> T
+        ): T {
+            // For coroutine tracing to work, trace spans must be added and removed even when
+            // tracing is not active (i.e. when TRACE_TAG_APP is disabled). Otherwise, when the
+            // coroutine resumes when tracing is active, we won't know its name.
+            val tracer = getTraceData(spanName)
+            val coroutineSpanCookie = tracer?.beginSpan(spanName.value) ?: INVALID_SPAN
+
+            // For now, also trace to "AsyncTraces". This will allow us to verify the correctness
+            // of the COROUTINE_TRACING feature flag.
+            val asyncTraceCookie =
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_APP))
+                    Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE)
+                else INVALID_SPAN
+            if (asyncTraceCookie != INVALID_SPAN) {
+                Trace.asyncTraceForTrackBegin(
+                    Trace.TRACE_TAG_APP,
+                    DEFAULT_TRACK_NAME,
+                    spanName.value,
+                    asyncTraceCookie
+                )
+            }
+            try {
+                return block()
+            } finally {
+                if (asyncTraceCookie != INVALID_SPAN) {
+                    Trace.asyncTraceForTrackEnd(
+                        Trace.TRACE_TAG_APP,
+                        DEFAULT_TRACK_NAME,
+                        asyncTraceCookie
+                    )
+                }
+                tracer?.endSpan(coroutineSpanCookie)
+            }
+        }
+
+        @OptIn(ExperimentalCoroutinesApi::class)
+        suspend fun getTraceData(spanName: Lazy<String>): TraceData? {
+            if (!coroutineTracingIsEnabled) {
+                logVerbose("Experimental flag COROUTINE_TRACING is off", spanName)
+            } else if (coroutineContext[TraceContextElement] == null) {
+                logVerbose("Current CoroutineContext is missing TraceContextElement", spanName)
+            } else {
+                return threadLocalTrace.get().also {
+                    if (it == null) logVerbose("ThreadLocal TraceData is null", spanName)
+                }
+            }
+            return null
+        }
+
+        private fun logVerbose(logMessage: String, spanName: Lazy<String>) {
+            if (DEBUG_COROUTINE_TRACING && Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "$logMessage. Dropping trace section: \"${spanName.value}\"")
+            }
+        }
+
+        /** @see traceCoroutine */
+        suspend inline fun <T> traceCoroutine(
+            spanName: String,
+            crossinline block: suspend () -> T
+        ): T = traceCoroutine(lazyOf(spanName)) { block() }
+
+        /** @see traceCoroutine */
+        suspend inline fun <T> traceCoroutine(
+            crossinline spanName: () -> String,
+            crossinline block: suspend () -> T
+        ): T = traceCoroutine(lazy(LazyThreadSafetyMode.PUBLICATION) { spanName() }) { block() }
+
+        /**
+         * Writes a trace message to indicate that a given section of code has begun running __on
+         * the current thread__. This must be followed by a corresponding call to [endSlice] in a
+         * reasonably short amount of time __on the same thread__ (i.e. _before_ the thread becomes
+         * idle again and starts running other, unrelated work).
+         *
+         * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as
+         * follows:
+         * ```
+         * Thread #1 | [==========================]
+         *           |       [==============]
+         *           |           [====]
+         * ```
+         *
+         * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is
+         * more verbose to call than [Trace.beginSection], but has the added benefit of not throwing
+         * an [IllegalArgumentException] if the provided string is longer than 127 characters. We
+         * use the term "slice" instead of "section" to be consistent with Perfetto.
+         *
+         * # Avoiding malformed traces
+         *
+         * Improper usage of this API will lead to malformed traces with long slices that sometimes
+         * never end. This will look like the following:
+         * ```
+         * Thread #1 | [===================================================================== ...
+         *           |       [==============]         [====================================== ...
+         *           |           [=======]              [======]       [===================== ...
+         *           |                                                       [=======]
+         * ```
+         *
+         * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks
+         * (instead, use [traceCoroutine] for tracing suspending functions). While it would be
+         * technically okay to call from a suspending function if that function were to only wrap
+         * non-suspending blocks with [beginSlice] and [endSlice], doing so is risky because suspend
+         * calls could be mistakenly added to that block as the code is refactored.
+         *
+         * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match
+         * it with a call to [endSlice] inside that callback, even if the callback runs on the same
+         * thread. Doing so would cause malformed traces because the [beginSlice] wasn't closed
+         * before the thread became idle and started running unrelated work.
+         *
+         * @param sliceName The name of the code section to appear in the trace
+         * @see endSlice
+         * @see traceCoroutine
+         */
+        fun beginSlice(sliceName: String) {
+            Trace.traceBegin(Trace.TRACE_TAG_APP, sliceName)
+        }
+
+        /**
+         * Writes a trace message to indicate that a given section of code has ended. This call must
+         * be preceded by a corresponding call to [beginSlice]. See [beginSlice] for important
+         * information regarding usage.
+         *
+         * @see beginSlice
+         * @see traceCoroutine
+         */
+        fun endSlice() {
+            Trace.traceEnd(Trace.TRACE_TAG_APP)
+        }
+
+        /**
+         * Writes a trace message indicating that an instant event occurred on the current thread.
+         * Unlike slices, instant events have no duration and do not need to be matched with another
+         * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+         * occurred:
+         * ```
+         * Thread #1 | [==============]               [======]
+         *           |     [====]                        ^
+         *           |        ^
+         * ```
+         *
+         * @param eventName The name of the event to appear in the trace.
+         */
+        fun instant(eventName: String) {
+            Trace.instant(Trace.TRACE_TAG_APP, eventName)
+        }
+
+        /**
+         * Writes a trace message indicating that an instant event occurred on the given track.
+         * Unlike slices, instant events have no duration and do not need to be matched with another
+         * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+         * occurred:
+         * ```
+         * Async  | [==============]               [======]
+         *  Track |     [====]                        ^
+         *   Name |        ^
+         * ```
+         *
+         * @param trackName The track where the event should appear in the trace.
+         * @param eventName The name of the event to appear in the trace.
+         */
+        fun instantForTrack(trackName: String, eventName: String) {
+            Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, eventName)
         }
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
new file mode 100644
index 0000000..4d8c545
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.instant
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CopyableThreadContextElement
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Used for safely persisting [TraceData] state when coroutines are suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+@OptIn(DelicateCoroutinesApi::class)
+@ExperimentalCoroutinesApi
+class TraceContextElement(private val traceData: TraceData = TraceData()) :
+    CopyableThreadContextElement<TraceData?> {
+
+    companion object Key : CoroutineContext.Key<TraceContextElement>
+
+    override val key: CoroutineContext.Key<TraceContextElement> = Key
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override fun updateThreadContext(context: CoroutineContext): TraceData? {
+        val oldState = threadLocalTrace.get()
+        oldState?.endAllOnThread()
+        threadLocalTrace.set(traceData)
+        instant("resuming ${context[CoroutineDispatcher]}")
+        traceData.beginAllOnThread()
+        return oldState
+    }
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
+        instant("suspending ${context[CoroutineDispatcher]}")
+        traceData.endAllOnThread()
+        threadLocalTrace.set(oldState)
+        oldState?.beginAllOnThread()
+    }
+
+    override fun copyForChild(): CopyableThreadContextElement<TraceData?> {
+        return TraceContextElement(traceData.copy())
+    }
+
+    override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
+        return TraceContextElement(traceData.copy())
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
new file mode 100644
index 0000000..0ae58fc
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.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.util.tracing
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.util.TraceUtils.Companion.beginSlice
+import com.android.systemui.util.TraceUtils.Companion.endSlice
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.random.Random
+
+/**
+ * Used for giving each thread a unique [TraceData] for thread-local storage. `null` by default.
+ * [threadLocalTrace] can only be used when it is paired with a [TraceContextElement].
+ *
+ * This ThreadLocal will be `null` if either 1) we aren't in a coroutine, or 2) the coroutine we are
+ * in does not have a [TraceContextElement].
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+val threadLocalTrace = ThreadLocal<TraceData?>()
+
+/**
+ * Used for storing trace sections so that they can be added and removed from the currently running
+ * thread when the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+class TraceData {
+    private var slices = mutableListOf<TraceSection>()
+
+    /** Adds current trace slices back to the current thread. Called when coroutine is resumed. */
+    fun beginAllOnThread() {
+        slices.forEach { beginSlice(it.name) }
+    }
+
+    /**
+     * Removes all current trace slices from the current thread. Called when coroutine is suspended.
+     */
+    fun endAllOnThread() {
+        for (i in 0..slices.size) {
+            endSlice()
+        }
+    }
+
+    /**
+     * Creates a new trace section with a unique ID and adds it to the current trace data. The slice
+     * will also be added to the current thread immediately. This slice will not propagate to parent
+     * coroutines, or to child coroutines that have already started. The unique ID is used to verify
+     * that the [endSpan] is corresponds to a [beginSpan].
+     */
+    fun beginSpan(name: String): Int {
+        val newSlice = TraceSection(name, Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE))
+        slices.add(newSlice)
+        beginSlice(name)
+        return newSlice.id
+    }
+
+    /**
+     * Used by [TraceContextElement] when launching a child coroutine so that the child coroutine's
+     * state is isolated from the parent.
+     */
+    fun copy(): TraceData {
+        return TraceData().also { it.slices.addAll(slices) }
+    }
+
+    /**
+     * Ends the trace section and validates it corresponds with an earlier call to [beginSpan]. The
+     * trace slice will immediately be removed from the current thread. This information will not
+     * propagate to parent coroutines, or to child coroutines that have already started.
+     */
+    fun endSpan(id: Int) {
+        val v = slices.removeLast()
+        if (v.id != id) {
+            if (STRICT_MODE) {
+                throw IllegalArgumentException(errorMsg)
+            } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, errorMsg)
+            }
+        }
+        endSlice()
+    }
+
+    companion object {
+        private const val TAG = "TraceData"
+        const val INVALID_SPAN = -1
+        const val FIRST_VALID_SPAN = 1
+
+        /**
+         * If true, throw an exception instead of printing a warning when trace sections beginnings
+         * and ends are mismatched.
+         */
+        private val STRICT_MODE = Build.IS_ENG
+
+        private const val errorMsg =
+            "Mismatched trace section. This likely means you are accessing the trace local " +
+                "storage (threadLocalTrace) without a corresponding CopyableThreadContextElement." +
+                " This could happen if you are using a global dispatcher like Dispatchers.IO." +
+                " To fix this, use one of the coroutine contexts provided by the dagger scope " +
+                "(e.g. \"@Main CoroutineContext\")."
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
new file mode 100644
index 0000000..b70c497
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.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.util.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+
+/**
+ * Represents a section of code executing in a coroutine. This can be split up into multiple slices
+ * on different threads as the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @param name the name of the slice to appear on the current thread's track.
+ * @param id used for matching the beginning and end of trace sections and validating correctness
+ * @see traceCoroutine
+ */
+data class TraceSection(
+    val name: String,
+    val id: Int,
+)
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 3360c96..aef8371 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
     val knownFlags: Map<String, Flag<*>>
         get() {
             // We need to access Flags in order to initialize our map.
-            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
             return flagMap
         }
 
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index fae9fec..a263361 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -39,6 +39,12 @@
     @IntoSet
     abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
 
+    @Binds
+    @IntoSet
+    abstract fun bindsNotOccludedCondition(
+        impl: NotOccludedCondition
+    ): ConditionalRestarter.Condition
+
     @Module
     companion object {
         @JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 75465c2..f4b4296 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
     val knownFlags: Map<String, Flag<*>>
         get() {
             // We need to access Flags in order to initialize our map.
-            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
             return flagMap
         }
 
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 7aacb4e..9684d5e 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -39,6 +39,12 @@
     @IntoSet
     abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
 
+    @Binds
+    @IntoSet
+    abstract fun bindsNotOccludedCondition(
+        impl: NotOccludedCondition
+    ): ConditionalRestarter.Condition
+
     @Module
     companion object {
         @JvmStatic
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index c4f1ce8..3757274 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -33,11 +33,11 @@
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
 import android.util.Log
-import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.settings.SecureSettings
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -50,6 +50,7 @@
     @Main private val handler: Handler,
     private val secureSettings: SecureSettings,
     private val contentResolver: ContentResolver,
+    private val selectedUserInteractor: SelectedUserInteractor,
     dumpManager: DumpManager
 ) : Dumpable {
 
@@ -134,7 +135,7 @@
                     )
             )
 
-            onChange(true, ArrayList(), 0, getCurrentUser())
+            onChange(true, ArrayList(), 0, selectedUserInteractor.getSelectedUserId())
         }
 
         private fun registerUri(uris: Collection<Uri>) {
@@ -153,29 +154,31 @@
             flags: Int,
             userId: Int
         ) {
-            if (getCurrentUser() != userId) {
+            if (selectedUserInteractor.getSelectedUserId() != userId) {
                 return
             }
 
             if (selfChange || uris.contains(wakeUri)) {
                 requestActiveUnlockOnWakeup = secureSettings.getIntForUser(
-                        ACTIVE_UNLOCK_ON_WAKE, 0, getCurrentUser()) == 1
+                        ACTIVE_UNLOCK_ON_WAKE, 0, selectedUserInteractor.getSelectedUserId()) == 1
             }
 
             if (selfChange || uris.contains(unlockIntentUri)) {
                 requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser(
-                        ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, getCurrentUser()) == 1
+                        ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0,
+                        selectedUserInteractor.getSelectedUserId()) == 1
             }
 
             if (selfChange || uris.contains(bioFailUri)) {
                 requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
-                        ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
+                        ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0,
+                        selectedUserInteractor.getSelectedUserId()) == 1
             }
 
             if (selfChange || uris.contains(faceErrorsUri)) {
                 processStringArray(
                         secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
-                                getCurrentUser()),
+                                selectedUserInteractor.getSelectedUserId()),
                         faceErrorsToTriggerBiometricFailOn,
                         setOf(FACE_ERROR_TIMEOUT))
             }
@@ -183,7 +186,7 @@
             if (selfChange || uris.contains(faceAcquireInfoUri)) {
                 processStringArray(
                         secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
-                                getCurrentUser()),
+                                selectedUserInteractor.getSelectedUserId()),
                         faceAcquireInfoToTriggerBiometricFailOn,
                         emptySet())
             }
@@ -192,7 +195,7 @@
                 processStringArray(
                         secureSettings.getStringForUser(
                                 ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-                                getCurrentUser()),
+                                selectedUserInteractor.getSelectedUserId()),
                         onUnlockIntentWhenBiometricEnrolled,
                         setOf(BiometricType.NONE.intValue))
             }
@@ -201,7 +204,7 @@
                 processStringArray(
                     secureSettings.getStringForUser(
                         ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
-                        getCurrentUser()),
+                        selectedUserInteractor.getSelectedUserId()),
                     wakeupsConsideredUnlockIntents,
                     setOf(WAKE_REASON_UNFOLD_DEVICE))
             }
@@ -210,7 +213,7 @@
                 processStringArray(
                     secureSettings.getStringForUser(
                         ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
-                        getCurrentUser()),
+                        selectedUserInteractor.getSelectedUserId()),
                     wakeupsToForceDismissKeyguard,
                     setOf(WAKE_REASON_UNFOLD_DEVICE))
             }
@@ -315,8 +318,8 @@
 
         keyguardUpdateMonitor?.let {
             val anyFaceEnrolled = it.isFaceEnrolled
-            val anyFingerprintEnrolled =
-                    it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
+            val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible(
+                    selectedUserInteractor.getSelectedUserId())
             val udfpsEnrolled = it.isUdfpsEnrolled
 
             if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
@@ -370,8 +373,8 @@
             pw.println("   shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
                     "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
             pw.println("   faceEnrolled=${it.isFaceEnrolled}")
-            pw.println("   fpEnrolled=${
-                    it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
+            pw.println("   fpUnlockPossible=${
+                it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}")
             pw.println("   udfpsEnrolled=${it.isUdfpsEnrolled}")
         } ?: pw.println("   keyguardUpdateMonitor is uninitialized")
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 207f344..58bbdeb 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -45,6 +45,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import java.util.NoSuchElementException;
 
@@ -63,6 +64,7 @@
     private Handler mHandler;
     private IKeyguardClient mClient;
     private KeyguardSecurityCallback mKeyguardCallback;
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private final ServiceConnection mConnection = new ServiceConnection() {
         @Override
@@ -76,7 +78,7 @@
                 } catch (RemoteException e) {
                     // Failed to link to death, just dismiss and unbind the service for now.
                     Log.e(TAG, "Lost connection to secondary lockscreen service", e);
-                    dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                    dismiss(mSelectedUserInteractor.getSelectedUserId());
                 }
             }
         }
@@ -110,7 +112,7 @@
                 mView.setChildSurfacePackage(surfacePackage);
             } else {
                 mHandler.post(() -> {
-                    dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                    dismiss(mSelectedUserInteractor.getSelectedUserId());
                 });
             }
         }
@@ -131,7 +133,7 @@
     protected SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
         @Override
         public void surfaceCreated(SurfaceHolder holder) {
-            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             mUpdateMonitor.registerCallback(mUpdateCallback);
 
             if (mClient != null) {
@@ -158,7 +160,7 @@
 
     private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
             KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
-            @Main Handler handler) {
+            @Main Handler handler, SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mHandler = handler;
         mParent = parent;
@@ -166,6 +168,7 @@
         mKeyguardCallback = callback;
         mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
         mView.setId(View.generateViewId());
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     /**
@@ -218,13 +221,13 @@
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error in onCreateKeyguardSurface", e);
-            dismiss(KeyguardUpdateMonitor.getCurrentUser());
+            dismiss(mSelectedUserInteractor.getSelectedUserId());
         }
     }
 
     private void dismiss(int userId) {
         mHandler.removeCallbacksAndMessages(null);
-        if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) {
+        if (mView.isAttachedToWindow() && userId == mSelectedUserInteractor.getSelectedUserId()) {
             hide();
             if (mKeyguardCallback != null) {
                 mKeyguardCallback.dismiss(/* securityVerified= */ true, userId,
@@ -265,19 +268,24 @@
         private final KeyguardSecurityContainer mParent;
         private final KeyguardUpdateMonitor mUpdateMonitor;
         private final Handler mHandler;
+        private final SelectedUserInteractor mSelectedUserInteractor;
 
         @Inject
-        public Factory(Context context, KeyguardSecurityContainer parent,
-                KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
+        public Factory(Context context,
+                KeyguardSecurityContainer parent,
+                KeyguardUpdateMonitor updateMonitor,
+                @Main Handler handler,
+                SelectedUserInteractor selectedUserInteractor) {
             mContext = context;
             mParent = parent;
             mUpdateMonitor = updateMonitor;
             mHandler = handler;
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
             return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
-                    callback, mHandler);
+                    callback, mHandler, mSelectedUserInteractor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 873c3d9..1cfa816 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -39,10 +39,10 @@
 
 import com.android.keyguard.logging.CarrierTextManagerLogger;
 import com.android.settingslib.WirelessUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
 import com.android.systemui.telephony.TelephonyListenerManager;
 
@@ -612,36 +612,6 @@
         return list;
     }
 
-    private CharSequence getCarrierHelpTextForSimState(int simState,
-            String plmn, String spn) {
-        int carrierHelpTextId = 0;
-        CarrierTextManager.StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case NetworkLocked:
-                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
-                break;
-
-            case SimMissing:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
-                break;
-
-            case SimPermDisabled:
-                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
-                break;
-
-            case SimMissingLocked:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
-                break;
-
-            case Normal:
-            case SimLocked:
-            case SimPukLocked:
-                break;
-        }
-
-        return mContext.getText(carrierHelpTextId);
-    }
-
     /** Injectable Buildeer for {@#link CarrierTextManager}. */
     public static class Builder {
         private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index eb7a735..e47d36f 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,8 @@
  */
 package com.android.keyguard
 
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -34,9 +36,10 @@
 import com.android.systemui.customization.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.DisplaySpecific
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
 import com.android.systemui.flags.Flags.REGION_SAMPLING
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -60,6 +63,7 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
 import java.util.Locale
 import java.util.TimeZone
@@ -79,7 +83,7 @@
     private val batteryController: BatteryController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val configurationController: ConfigurationController,
-    @Main private val resources: Resources,
+    @DisplaySpecific private val resources: Resources,
     private val context: Context,
     @Main private val mainExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
@@ -296,7 +300,7 @@
         object : KeyguardUpdateMonitorCallback() {
             override fun onKeyguardVisibilityChanged(visible: Boolean) {
                 isKeyguardVisible = visible
-                if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+                if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
                     if (!isKeyguardVisible) {
                         clock?.run {
                             smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -341,9 +345,9 @@
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
         disposableHandle =
             parent.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
                     listenForDozing(this)
-                    if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+                    if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         listenForDozeAmountTransition(this)
                         listenForAnyStateToAodTransition(this)
                     } else {
@@ -453,8 +457,9 @@
     @VisibleForTesting
     internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor.anyStateToAodTransition
-                .filter { it.transitionState == TransitionState.FINISHED }
+            keyguardTransitionInteractor.transitionStepsToState(AOD)
+                .filter { it.transitionState == TransitionState.STARTED }
+                .filter { it.from != LOCKSCREEN }
                 .collect { handleDoze(1f) }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index f7e8eb4..5de370f 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -43,6 +43,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.ViewController;
 
@@ -67,6 +68,7 @@
     private LockPatternUtils mLockPatternUtils;
     private Executor mMainExecutor;
     private Executor mBackgroundExecutor;
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private final KeyguardUpdateMonitorCallback mInfoCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -96,7 +98,8 @@
             ShadeController shadeController,
             @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
             LockPatternUtils lockPatternUtils,
-            Executor mainExecutor, Executor backgroundExecutor) {
+            Executor mainExecutor, Executor backgroundExecutor,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view);
         mConfigurationController = configurationController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -109,6 +112,7 @@
         mLockPatternUtils = lockPatternUtils;
         mMainExecutor = mainExecutor;
         mBackgroundExecutor = backgroundExecutor;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     @Override
@@ -142,7 +146,7 @@
             mBackgroundExecutor.execute(() -> {
                 boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
                 boolean isSecure = mLockPatternUtils
-                        .isSecure(KeyguardUpdateMonitor.getCurrentUser());
+                        .isSecure(mSelectedUserInteractor.getSelectedUserId());
                 mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
                         /* isInCall= */ isInCall,
                         /* hasTelephonyRadio= */ getContext().getPackageManager()
@@ -192,7 +196,7 @@
 
                     getContext().startActivityAsUser(emergencyDialIntent,
                             ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
-                            new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+                            new UserHandle(mSelectedUserInteractor.getSelectedUserId()));
                 }
             });
         });
@@ -218,6 +222,7 @@
         private final LockPatternUtils mLockPatternUtils;
         private final Executor mMainExecutor;
         private final Executor mBackgroundExecutor;
+        private final SelectedUserInteractor mSelectedUserInteractor;
 
         @Inject
         public Factory(ConfigurationController configurationController,
@@ -227,7 +232,8 @@
                 @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
                 LockPatternUtils lockPatternUtils,
                 @Main Executor mainExecutor,
-                @Background Executor backgroundExecutor) {
+                @Background Executor backgroundExecutor,
+                SelectedUserInteractor selectedUserInteractor) {
 
             mConfigurationController = configurationController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -240,6 +246,7 @@
             mLockPatternUtils = lockPatternUtils;
             mMainExecutor = mainExecutor;
             mBackgroundExecutor = backgroundExecutor;
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
@@ -247,7 +254,7 @@
             return new EmergencyButtonController(view, mConfigurationController,
                     mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
                     mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
-                    mMainExecutor, mBackgroundExecutor);
+                    mMainExecutor, mBackgroundExecutor, mSelectedUserInteractor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 167bd59..dad4400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -34,10 +34,11 @@
 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -80,9 +81,9 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
-                messageAreaControllerFactory, featureFlags);
+                messageAreaControllerFactory, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
@@ -104,7 +105,7 @@
         mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
         // if the user is currently locked out, enforce it.
         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         if (shouldLockout(deadline)) {
             handleAttemptLockout(deadline);
         }
@@ -175,7 +176,7 @@
     }
 
     void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
-        boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+        boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
         if (matched) {
             getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
             if (dismissKeyguard) {
@@ -212,7 +213,7 @@
             mPendingLockCheck.cancel(false);
         }
 
-        final int userId = KeyguardUpdateMonitor.getCurrentUser();
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
             // to avoid accidental lockout, only count attempts that are long enough to be a
             // real password. This may require some tweaking.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index e6a2bfa..d26caa9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -42,7 +43,8 @@
 class KeyguardBiometricLockoutLogger @Inject constructor(
     private val uiEventLogger: UiEventLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val sessionTracker: SessionTracker
+    private val sessionTracker: SessionTracker,
+    private val selectedUserInteractor: SelectedUserInteractor
 ) : CoreStartable {
     private var fingerprintLockedOut = false
     private var faceLockedOut = false
@@ -52,7 +54,7 @@
 
     override fun start() {
         mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
-                KeyguardUpdateMonitor.getCurrentUser())
+                selectedUserInteractor.getSelectedUserId())
         keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
     }
 
@@ -79,7 +81,7 @@
         }
 
         override fun onStrongAuthStateChanged(userId: Int) {
-            if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+            if (userId != selectedUserInteractor.getSelectedUserId()) {
                 return
             }
             val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index f6a0563..9bddcd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -22,10 +22,10 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
-import com.android.systemui.res.R;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.ClockController;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.DefaultClockController;
 
 import java.io.PrintWriter;
@@ -452,6 +452,10 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
+        // TODO: b/305022530
+        if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) {
+            mClock.getEvents().onColorPaletteChanged(mContext.getResources());
+        }
 
         if (changed) {
             post(() -> updateClockTargetRegions());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3c8301f..7bf3e8f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -38,10 +38,10 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
+import com.android.systemui.common.ui.ConfigurationState;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -50,14 +50,22 @@
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.regionsampling.RegionSampler;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
+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.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -68,6 +76,8 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.DisposableHandle;
+
 /**
  * Injectable controller for {@link KeyguardClockSwitch}.
  */
@@ -84,6 +94,12 @@
     private final DumpManager mDumpManager;
     private final ClockEventController mClockEventController;
     private final LogBuffer mLogBuffer;
+    private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
+    private final ConfigurationState mConfigurationState;
+    private final ConfigurationController mConfigurationController;
+    private final DozeParameters mDozeParameters;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
+    private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
     private FrameLayout mSmallClockFrame; // top aligned clock
     private FrameLayout mLargeClockFrame; // centered clock
 
@@ -107,10 +123,13 @@
     private boolean mShownOnSecondaryDisplay = false;
     private boolean mOnlyClock = false;
     private boolean mIsActiveDreamLockscreenHosted = false;
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlagsClassic mFeatureFlags;
     private KeyguardInteractor mKeyguardInteractor;
     private final DelayableExecutor mUiExecutor;
     private boolean mCanShowDoubleLineClock = true;
+    private DisposableHandle mAodIconsBindJob;
+    @Nullable private NotificationIconContainer mAodIconContainer;
+
     @VisibleForTesting
     final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
             (Boolean isLockscreenHosted) -> {
@@ -151,26 +170,38 @@
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
             LockscreenSmartspaceController smartspaceController,
+            ConfigurationController configurationController,
+            ScreenOffAnimationController screenOffAnimationController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SecureSettings secureSettings,
             @Main DelayableExecutor uiExecutor,
             DumpManager dumpManager,
             ClockEventController clockEventController,
             @KeyguardClockLog LogBuffer logBuffer,
+            NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel,
+            ConfigurationState configurationState,
+            DozeParameters dozeParameters,
+            AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
             KeyguardInteractor keyguardInteractor,
-            FeatureFlags featureFlags) {
+            FeatureFlagsClassic featureFlags) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mClockRegistry = clockRegistry;
         mKeyguardSliceViewController = keyguardSliceViewController;
         mNotificationIconAreaController = notificationIconAreaController;
         mSmartspaceController = smartspaceController;
+        mConfigurationController = configurationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         mSecureSettings = secureSettings;
         mUiExecutor = uiExecutor;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mDumpManager = dumpManager;
         mClockEventController = clockEventController;
         mLogBuffer = logBuffer;
+        mAodIconsViewModel = aodIconsViewModel;
+        mConfigurationState = configurationState;
+        mDozeParameters = dozeParameters;
+        mAodIconViewStore = aodIconViewStore;
         mView.setLogBuffer(mLogBuffer);
         mFeatureFlags = featureFlags;
         mKeyguardInteractor = keyguardInteractor;
@@ -316,6 +347,8 @@
     int getNotificationIconAreaHeight() {
         if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
             return 0;
+        } else if (NotificationIconContainerRefactor.isEnabled()) {
+            return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
         } else {
             return mNotificationIconAreaController.getHeight();
         }
@@ -533,7 +566,27 @@
             NotificationIconContainer nic = (NotificationIconContainer)
                     mView.findViewById(
                             com.android.systemui.res.R.id.left_aligned_notification_icon_container);
-            mNotificationIconAreaController.setupAodIcons(nic);
+            if (NotificationIconContainerRefactor.isEnabled()) {
+                if (mAodIconsBindJob != null) {
+                    mAodIconsBindJob.dispose();
+                }
+                if (nic != null) {
+                    nic.setOnLockScreen(true);
+                    mAodIconsBindJob = NotificationIconContainerViewBinder.bind(
+                        nic,
+                        mAodIconsViewModel,
+                        mConfigurationState,
+                        mConfigurationController,
+                        mDozeParameters,
+                        mFeatureFlags,
+                        mScreenOffAnimationController,
+                        mAodIconViewStore
+                    );
+                    mAodIconContainer = nic;
+                }
+            } else {
+                mNotificationIconAreaController.setupAodIcons(nic);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 29ce18c..b309483 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -29,7 +29,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.ui.BouncerMessageView;
 import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
@@ -38,7 +37,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -51,7 +52,6 @@
 
     private final SecurityMode mSecurityMode;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
-    private final EmergencyButton mEmergencyButton;
     private final EmergencyButtonController mEmergencyButtonController;
     private boolean mPaused;
     protected KeyguardMessageAreaController<BouncerKeyguardMessageArea> mMessageAreaController;
@@ -61,18 +61,20 @@
     // state for the current security method.
     private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
     private final FeatureFlags mFeatureFlags;
+    protected final SelectedUserInteractor mSelectedUserInteractor;
 
     protected KeyguardInputViewController(T view, SecurityMode securityMode,
             KeyguardSecurityCallback keyguardSecurityCallback,
             EmergencyButtonController emergencyButtonController,
             @Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view);
         mSecurityMode = securityMode;
         mKeyguardSecurityCallback = keyguardSecurityCallback;
-        mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
         mEmergencyButtonController = emergencyButtonController;
         mFeatureFlags = featureFlags;
+        mSelectedUserInteractor = selectedUserInteractor;
         if (messageAreaControllerFactory != null) {
             try {
                 BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
@@ -207,6 +209,7 @@
         private final DevicePostureController mDevicePostureController;
         private final KeyguardViewController mKeyguardViewController;
         private final FeatureFlags mFeatureFlags;
+        private final SelectedUserInteractor mSelectedUserInteractor;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -219,7 +222,7 @@
                 EmergencyButtonController.Factory emergencyButtonControllerFactory,
                 DevicePostureController devicePostureController,
                 KeyguardViewController keyguardViewController,
-                FeatureFlags featureFlags) {
+                FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -234,6 +237,7 @@
             mDevicePostureController = devicePostureController;
             mKeyguardViewController = keyguardViewController;
             mFeatureFlags = featureFlags;
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -248,32 +252,32 @@
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
                         emergencyButtonController, mMessageAreaControllerFactory,
-                        mDevicePostureController, mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardPasswordView) {
                 return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
                         mFalsingCollector, mKeyguardViewController,
-                        mDevicePostureController, mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
-                        mDevicePostureController, mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 50be97e..3585feb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -44,8 +44,6 @@
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -66,18 +64,8 @@
  */
 public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
 
-    private final int mDisappearYTranslation;
-
-    private static final long IME_DISAPPEAR_DURATION_MS = 125;
-
-    // A delay constant to be used in a workaround for the situation where InputMethodManagerService
-    // is not switched to the new user yet.
-    // TODO: Remove this by ensuring such a race condition never happens.
-
     private TextView mPasswordEntry;
     private TextViewInputDisabler mPasswordEntryDisabler;
-    private Interpolator mLinearOutSlowInInterpolator;
-    private Interpolator mFastOutLinearInInterpolator;
     private DisappearAnimationListener mDisappearAnimationListener;
     @Nullable private MotionLayout mContainerMotionLayout;
     private boolean mAlreadyUsingSplitBouncer = false;
@@ -93,12 +81,6 @@
 
     public KeyguardPasswordView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDisappearYTranslation = getResources().getDimensionPixelSize(
-                R.dimen.disappear_y_translation);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                context, android.R.interpolator.linear_out_slow_in);
-        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
-                context, android.R.interpolator.fast_out_linear_in);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 959cf6f..3d8aaaf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -39,11 +39,12 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
@@ -51,7 +52,6 @@
 public class KeyguardPasswordViewController
         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
 
-    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final DevicePostureController mPostureController;
     private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -65,15 +65,23 @@
     private boolean mPaused;
 
     private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
-        // Check if this was the result of hitting the enter key
+        // Check if this was the result of hitting the IME done action
         final boolean isSoftImeEvent = event == null
                 && (actionId == EditorInfo.IME_NULL
                 || actionId == EditorInfo.IME_ACTION_DONE
                 || actionId == EditorInfo.IME_ACTION_NEXT);
-        final boolean isKeyboardEnterKey = event != null
-                && KeyEvent.isConfirmKey(event.getKeyCode())
-                && event.getAction() == KeyEvent.ACTION_DOWN;
-        if (isSoftImeEvent || isKeyboardEnterKey) {
+        if (isSoftImeEvent) {
+            verifyPasswordAndUnlock();
+            return true;
+        }
+        return false;
+    };
+
+    private final View.OnKeyListener mKeyListener = (v, keyCode, keyEvent) -> {
+        final boolean isKeyboardEnterKey = keyEvent != null
+                && KeyEvent.isConfirmKey(keyCode)
+                && keyEvent.getAction() == KeyEvent.ACTION_UP;
+        if (isKeyboardEnterKey) {
             verifyPasswordAndUnlock();
             return true;
         }
@@ -112,10 +120,11 @@
             FalsingCollector falsingCollector,
             KeyguardViewController keyguardViewController,
             DevicePostureController postureController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
-                emergencyButtonController, featureFlags);
+                emergencyButtonController, featureFlags, selectedUserInteractor);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
         mPostureController = postureController;
@@ -132,21 +141,23 @@
     @Override
     protected void onViewAttached() {
         super.onViewAttached();
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mPasswordEntry.setTextOperationUser(
+                UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
         mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
 
         mView.onDevicePostureChanged(mPostureController.getDevicePosture());
+
         mPostureController.addCallback(mPostureCallback);
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
         mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
+        mPasswordEntry.setOnKeyListener(mKeyListener);
         mPasswordEntry.addTextChangedListener(mTextWatcher);
         // Poke the wakelock any time the text is selected or modified
         mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
-
         mSwitchImeButton.setOnClickListener(v -> {
             mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
             // Do not show auxiliary subtypes in password lock screen.
@@ -164,13 +175,6 @@
 
         // If there's more than one IME, enable the IME switcher button
         updateSwitchImeButton();
-
-        // When we the current user is switching, InputMethodManagerService sometimes has not
-        // switched internal state yet here. As a quick workaround, we check the keyboard state
-        // again.
-        // TODO: Remove this workaround by ensuring such a race condition never happens.
-        mMainExecutor.executeDelayed(
-                this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
     }
 
     @Override
@@ -187,7 +191,8 @@
 
     @Override
     void resetState() {
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mPasswordEntry.setTextOperationUser(
+                UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
         mMessageAreaController.setMessage(getInitialMessageResId());
         final boolean wasDisabled = mPasswordEntry.isEnabled();
         mView.setPasswordEntryEnabled(true);
@@ -280,7 +285,7 @@
             final boolean shouldIncludeAuxiliarySubtypes) {
         final List<InputMethodInfo> enabledImis =
                 imm.getEnabledInputMethodListAsUser(
-                        UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+                        UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
 
         // Number of the filtered IMEs
         int filteredImisCount = 0;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 57151ae..db7ff88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -36,11 +36,12 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import java.util.HashMap;
 import java.util.List;
@@ -110,7 +111,7 @@
                 mPendingLockCheck.cancel(false);
             }
 
-            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                 // Treat single-sized patterns as erroneous taps.
                 if (pattern.size() == 1) {
@@ -163,7 +164,7 @@
 
         private void onPatternChecked(int userId, boolean matched, int timeoutMs,
                 boolean isValidPattern) {
-            boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+            boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
             if (matched) {
                 getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
                 if (dismissKeyguard) {
@@ -198,9 +199,10 @@
             FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            DevicePostureController postureController, FeatureFlags featureFlags) {
+            DevicePostureController postureController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
-                messageAreaControllerFactory, featureFlags);
+                messageAreaControllerFactory, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
@@ -223,7 +225,7 @@
         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
         mLockPatternView.setSaveEnabled(false);
         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
+                mSelectedUserInteractor.getSelectedUserId()));
         mLockPatternView.setOnTouchListener((v, event) -> {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 mFalsingCollector.avoidGesture();
@@ -243,7 +245,7 @@
         mPostureController.addCallback(mPostureCallback);
         // if the user is currently locked out, enforce it.
         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         if (deadline != 0) {
             handleAttemptLockout(deadline);
         }
@@ -266,7 +268,7 @@
     public void reset() {
         // reset lock pattern
         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
+                mSelectedUserInteractor.getSelectedUserId()));
         mLockPatternView.enableInput();
         mLockPatternView.setEnabled(true);
         mLockPatternView.clearPattern();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 681aa70..175544d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -89,10 +89,7 @@
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (KeyEvent.isConfirmKey(keyCode)) {
-            mOkButton.performClick();
-            return true;
-        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+        if (keyCode == KeyEvent.KEYCODE_DEL) {
             mDeleteButton.performClick();
             return true;
         }
@@ -110,6 +107,15 @@
     }
 
     @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            mOkButton.performClick();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
     protected int getPromptReasonStringRes(int reason) {
         switch (reason) {
             case PROMPT_REASON_RESTART:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index aacf866..376933d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -25,9 +25,10 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
         extends KeyguardAbsKeyInputViewController<T> {
@@ -40,6 +41,9 @@
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             return mView.onKeyDown(keyCode, event);
         }
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            return mView.onKeyUp(keyCode, event);
+        }
         return false;
     };
 
@@ -60,10 +64,11 @@
             LiftToActivateListener liftToActivateListener,
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
-                emergencyButtonController, featureFlags);
+                emergencyButtonController, featureFlags, selectedUserInteractor);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
@@ -74,7 +79,7 @@
         super.onViewAttached();
 
         boolean showAnimations = !mLockPatternUtils
-                .isPinEnhancedPrivacyEnabled(KeyguardUpdateMonitor.getCurrentUser());
+                .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
         mPasswordEntry.setShowPassword(showAnimations);
         for (NumPadKey button : mView.getButtons()) {
             button.setOnTouchListener((v, event) -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 9a78868..947d90f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -23,11 +23,12 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 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.user.domain.interactor.SelectedUserInteractor;
 
 public class KeyguardPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardPINView> {
@@ -55,17 +56,17 @@
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
             DevicePostureController postureController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
         mFeatureFlags = featureFlags;
         view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
         mBackspaceKey = view.findViewById(R.id.delete_button);
-        mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
+        mPinLength = mLockPatternUtils.getPinLength(selectedUserInteractor.getSelectedUserId());
     }
 
     @Override
@@ -124,7 +125,7 @@
 
     private void updateAutoConfirmationState() {
         mDisabledAutoConfirmation = mLockPatternUtils.getCurrentFailedPasswordAttempts(
-                KeyguardUpdateMonitor.getCurrentUser()) >= MIN_FAILED_PIN_ATTEMPTS;
+                mSelectedUserInteractor.getSelectedUserId()) >= MIN_FAILED_PIN_ATTEMPTS;
         updateOKButtonVisibility();
         updateBackSpaceVisibility();
         updatePinHinting();
@@ -179,7 +180,8 @@
      */
     private boolean isAutoPinConfirmEnabledInSettings() {
         //Checks if user has enabled the auto confirm in Settings
-        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser())
+        return mLockPatternUtils.isAutoPinConfirmEnabled(
+                mSelectedUserInteractor.getSelectedUserId())
                 && mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 51dafac..1b6112f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -91,7 +91,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.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -157,23 +157,25 @@
     private int mCurrentUser = UserHandle.USER_NULL;
     private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
             new UserSwitcherController.UserSwitchCallback() {
-        @Override
-        public void onUserSwitched() {
-            if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) {
-                return;
-            }
-            mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
-            showPrimarySecurityScreen(false);
-            if (mCurrentSecurityMode != SecurityMode.SimPin
-                    && mCurrentSecurityMode != SecurityMode.SimPuk) {
-                reinflateViewFlipper((l) -> {});
-            }
-        }
-    };
+                @Override
+                public void onUserSwitched() {
+                    if (mCurrentUser == mSelectedUserInteractor.getSelectedUserId()) {
+                        return;
+                    }
+                    mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
+                    showPrimarySecurityScreen(false);
+                    if (mCurrentSecurityMode != SecurityMode.SimPin
+                            && mCurrentSecurityMode != SecurityMode.SimPuk) {
+                        reinflateViewFlipper((l) -> {
+                        });
+                    }
+                }
+            };
 
     @VisibleForTesting
     final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
         private MotionEvent mTouchDown;
+
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
             return false;
@@ -267,7 +269,8 @@
                 ThreadUtils.postOnBackgroundThread(() -> {
                     try {
                         Thread.sleep(5000);
-                    } catch (InterruptedException ignored) { }
+                    } catch (InterruptedException ignored) {
+                    }
                     System.gc();
                     System.runFinalization();
                     System.gc();
@@ -281,7 +284,7 @@
             mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
                     .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
             mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
-                            : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
+                    : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
         }
 
         @Override
@@ -404,7 +407,7 @@
                         }
                         mKeyguardSecurityCallback.dismiss(
                                 false /* authenticated */,
-                                KeyguardUpdateMonitor.getCurrentUser(),
+                                mSelectedUserInteractor.getSelectedUserId(),
                                 /* bypassSecondaryLockScreen */ false,
                                 SecurityMode.Invalid
                         );
@@ -420,12 +423,13 @@
                     showPrimarySecurityScreen(false);
                 }
             };
-    private final UserInteractor mUserInteractor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
     private final Provider<JavaAdapter> mJavaAdapter;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
-    @Nullable private Job mSceneTransitionCollectionJob;
+    @Nullable
+    private Job mSceneTransitionCollectionJob;
 
     @Inject
     public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -453,7 +457,7 @@
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             BouncerMessageInteractor bouncerMessageInteractor,
             Provider<JavaAdapter> javaAdapter,
-            UserInteractor userInteractor,
+            SelectedUserInteractor selectedUserInteractor,
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -487,7 +491,7 @@
         mAudioManager = audioManager;
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mBouncerMessageInteractor = bouncerMessageInteractor;
-        mUserInteractor = userInteractor;
+        mSelectedUserInteractor = selectedUserInteractor;
         mDeviceEntryInteractor = deviceEntryInteractor;
         mJavaAdapter = javaAdapter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -520,10 +524,10 @@
             // When the scene framework says that the lockscreen has been dismissed, dismiss the
             // keyguard here, revealing the underlying app or launcher:
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
-                mDeviceEntryInteractor.get().isDeviceEntered(),
+                    mDeviceEntryInteractor.get().isDeviceEntered(),
                     isDeviceEntered -> {
                     if (isDeviceEntered) {
-                        final int selectedUserId = mUserInteractor.getSelectedUserId();
+                        final int selectedUserId = mSelectedUserInteractor.getSelectedUserId();
                         showNextSecurityScreenOrFinish(
                             /* authenticated= */ true,
                             selectedUserId,
@@ -548,7 +552,7 @@
         }
     }
 
-    /** */
+    /**  */
     public void onPause() {
         if (DEBUG) {
             Log.d(TAG, String.format("screen off, instance %s at %s",
@@ -586,12 +590,13 @@
     /**
      * Shows the primary security screen for the user. This will be either the multi-selector
      * or the user's security method.
+     *
      * @param turningOff true if the device is being turned off
      */
     public void showPrimarySecurityScreen(boolean turningOff) {
         if (DEBUG) Log.d(TAG, "show()");
         SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser()));
+                mSelectedUserInteractor.getSelectedUserId()));
         if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
         showSecurityScreen(securityMode);
     }
@@ -671,6 +676,7 @@
 
     /**
      * Dismisses the keyguard by going to the next screen or making it gone.
+     *
      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
      * @return True if the keyguard is done.
      */
@@ -716,7 +722,7 @@
     }
 
     /**
-     *  Resets the state of the views.
+     * Resets the state of the views.
      */
     public void reset() {
         mView.reset();
@@ -748,8 +754,8 @@
             getCurrentSecurityController(controller -> controller.onResume(reason));
         }
         mView.onResume(
-                mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
-                mKeyguardStateController.isFaceAuthEnabled());
+                mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()),
+                mKeyguardStateController.isFaceEnrolled());
     }
 
     /** Sets an initial message that would override the default message */
@@ -764,7 +770,6 @@
 
     /**
      * Show the bouncer and start appear animations.
-     *
      */
     public void appear() {
         // We might still be collapsed and the view didn't have time to layout yet or still
@@ -823,13 +828,16 @@
 
     /**
      * Shows the next security screen if there is one.
-     * @param authenticated true if the user entered the correct authentication
-     * @param targetUserId a user that needs to be the foreground user at the finish (if called)
-     *     completion.
+     *
+     * @param authenticated             true if the user entered the correct authentication
+     * @param targetUserId              a user that needs to be the foreground user at the finish
+     *                                  (if called)
+     *                                  completion.
      * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
-     *     secondary lock screen requirement, if any.
-     * @param expectedSecurityMode SecurityMode that is invoking this request. SecurityMode.Invalid
-     *      indicates that no check should be done
+     *                                  secondary lock screen requirement, if any.
+     * @param expectedSecurityMode      SecurityMode that is invoking this request.
+     *                                  SecurityMode.Invalid
+     *                                  indicates that no check should be done
      * @return true if keyguard is done
      */
     public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
@@ -879,7 +887,7 @@
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
                     boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser())
+                            mSelectedUserInteractor.getSelectedUserId())
                             || !mDeviceProvisionedController.isUserSetup(targetUserId);
 
                     if (securityMode == SecurityMode.None && isLockscreenDisabled) {
@@ -955,6 +963,7 @@
      * Allows the media keys to work when the keyguard is showing.
      * The media keys should be of no interest to the actual keyguard view(s),
      * so intercepting them here should not be of any harm.
+     *
      * @param event The key event
      * @return whether the event was consumed as a media key.
      */
@@ -1050,8 +1059,6 @@
     /**
      * Switches to the given security view unless it's already being shown, in which case
      * this is a no-op.
-     *
-     * @param securityMode
      */
     @VisibleForTesting
     void showSecurityScreen(SecurityMode securityMode) {
@@ -1230,6 +1237,7 @@
      * Fades and translates in/out the security screen.
      * Fades in as expansion approaches 0.
      * Animation duration is between 0.33f and 0.67f of panel expansion fraction.
+     *
      * @param fraction amount of the screen that should show.
      */
     public void setExpansion(float fraction) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 8717a53..6e24208 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -31,6 +31,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
@@ -39,9 +40,10 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public class KeyguardSimPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -82,10 +84,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
-            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
+            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -167,7 +170,7 @@
                             mRemainingAttempts = -1;
                             mShowDefaultMessage = true;
                             getKeyguardSecurityCallback().dismiss(
-                                    true, KeyguardUpdateMonitor.getCurrentUser(),
+                                    true, mSelectedUserInteractor.getSelectedUserId(),
                                     SecurityMode.SimPin);
                         } else {
                             mShowDefaultMessage = false;
@@ -324,7 +327,11 @@
         } else {
             SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
-            msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            if (!TextUtils.isEmpty(displayName)) {
+                msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            } else {
+                msg = rez.getString(R.string.kg_sim_pin_instructions);
+            }
             if (info != null) {
                 color = info.getIconTint();
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 248b7af..05fb5fa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -29,6 +29,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManager;
 import android.widget.ImageView;
@@ -36,9 +37,10 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public class KeyguardSimPukViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -69,7 +71,8 @@
             if (simState == TelephonyManager.SIM_STATE_READY) {
                 mRemainingAttempts = -1;
                 mShowDefaultMessage = true;
-                getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser(),
+                getKeyguardSecurityCallback().dismiss(
+                        true, mSelectedUserInteractor.getSelectedUserId(),
                         SecurityMode.SimPuk);
             } else {
                 resetState();
@@ -86,10 +89,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
-            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
+            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -206,7 +210,11 @@
         } else {
             SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
-            msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            if (!TextUtils.isEmpty(displayName)) {
+                msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            } else {
+                msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+            }
             if (info != null) {
                 color = info.getIconTint();
             }
@@ -238,7 +246,7 @@
 
     private boolean checkPuk() {
         // make sure the puk is at least 8 digits long.
-        if (mPasswordEntry.getText().length() == 8) {
+        if (mPasswordEntry.getText().length() >= 8) {
             mPukText = mPasswordEntry.getText();
             return true;
         }
@@ -279,7 +287,7 @@
                             mShowDefaultMessage = true;
 
                             getKeyguardSecurityCallback().dismiss(
-                                    true, KeyguardUpdateMonitor.getCurrentUser(),
+                                    true, mSelectedUserInteractor.getSelectedUserId(),
                                     SecurityMode.SimPuk);
                         } else {
                             mShowDefaultMessage = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 79642bd..758d1fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -54,6 +54,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -102,10 +105,11 @@
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
-
+    private boolean mGoneToAodTransitionRunning = false;
     private DumpManager mDumpManager;
 
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -135,6 +139,7 @@
             FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
             KeyguardInteractor keyguardInteractor,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
             DumpManager dumpManager,
             PowerInteractor powerInteractor) {
         super(keyguardStatusView);
@@ -144,12 +149,13 @@
         mConfigurationController = configurationController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
-                logger.getBuffer());
+                featureFlags, logger.getBuffer());
         mInteractionJankMonitor = interactionJankMonitor;
         mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -199,6 +205,15 @@
                         dozeTimeTick();
                     }
                 }, context);
+
+        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+                (TransitionStep step) -> {
+                    if (step.getTransitionState() == TransitionState.RUNNING) {
+                        mGoneToAodTransitionRunning = true;
+                    } else {
+                        mGoneToAodTransitionRunning = false;
+                    }
+                }, context);
     }
 
     public KeyguardStatusView getView() {
@@ -266,7 +281,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
             mView.setAlpha(alpha);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3bf1482..f19a9ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -153,7 +153,6 @@
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -177,6 +176,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -186,8 +186,8 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.Assert;
-import com.android.systemui.util.settings.SecureSettings;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -410,7 +410,6 @@
     private final DevicePolicyManager mDevicePolicyManager;
     private final DevicePostureController mPostureController;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final SecureSettings mSecureSettings;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private final StatusBarStateController mStatusBarStateController;
@@ -429,6 +428,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final WakefulnessLifecycle mWakefulness;
     private final DisplayTracker mDisplayTracker;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -441,7 +441,6 @@
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
-    private boolean mIsFaceEnrolled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private int mPostureState = DEVICE_POSTURE_UNKNOWN;
     private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
@@ -537,13 +536,14 @@
 
     private static int sCurrentUser;
 
+    @Deprecated
     public synchronized static void setCurrentUser(int currentUser) {
         sCurrentUser = currentUser;
     }
 
     /**
      * @deprecated This can potentially return unexpected values in a multi user scenario
-     * as this state is managed by another component. Consider using {@link UserTracker}.
+     * as this state is managed by another component. Consider using {@link SelectedUserInteractor}.
      */
     @Deprecated
     public synchronized static int getCurrentUser() {
@@ -577,7 +577,7 @@
 
         if (enabled) {
             String message = null;
-            if (KeyguardUpdateMonitor.getCurrentUser() == userId
+            if (mSelectedUserInteractor.getSelectedUserId() == userId
                     && trustGrantedMessages != null) {
                 // Show the first non-empty string provided by a trust agent OR intentionally pass
                 // an empty string through (to prevent the default trust agent string from showing)
@@ -590,7 +590,7 @@
             }
 
             mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message);
-            if (userId == getCurrentUser()) {
+            if (userId == mSelectedUserInteractor.getSelectedUserId()) {
                 if (newlyUnlocked) {
                     // if this callback is ever removed, this should then be logged in
                     // TrustRepository
@@ -1038,7 +1038,7 @@
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
         try {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             if (userId != authUserId) {
                 mLogger.logFingerprintAuthForWrongUser(authUserId);
                 return;
@@ -1127,8 +1127,8 @@
             lockedOutStateChanged = !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
             mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
-            mLockPatternUtils.requireStrongAuth(
-                    STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
+            mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+                    mSelectedUserInteractor.getSelectedUserId());
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
@@ -1250,6 +1250,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
         String reason =
@@ -1278,6 +1279,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1299,6 +1301,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
@@ -1306,7 +1309,7 @@
                 mLogger.d("Aborted successful auth because device is going to sleep.");
                 return;
             }
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             if (userId != authUserId) {
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
@@ -1327,6 +1330,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceHelp(int msgId, String helpString) {
         if (mFaceAcquiredInfoIgnoreList.contains(msgId)) {
             return;
@@ -1344,6 +1348,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
         String errString = originalErrMsg;
@@ -1560,7 +1565,8 @@
     @Deprecated
     public boolean getIsFaceAuthenticated() {
         boolean faceAuthenticated = false;
-        BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
+        BiometricAuthenticated bioFaceAuthenticated =
+                mUserFaceAuthenticated.get(mSelectedUserInteractor.getSelectedUserId());
         if (bioFaceAuthenticated != null) {
             faceAuthenticated = bioFaceAuthenticated.mAuthenticated;
         }
@@ -1749,9 +1755,10 @@
                 cb.onStrongAuthStateChanged(userId);
             }
         }
-        if (userId == getCurrentUser()) {
+        if (userId == mSelectedUserInteractor.getSelectedUserId()) {
             FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
-                    mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()));
+                    mStrongAuthTracker.getStrongAuthForUser(
+                            mSelectedUserInteractor.getSelectedUserId()));
 
             // Strong auth is only reset when primary auth is used to enter the device,
             // so we only check whether to stop biometric listening states here
@@ -1778,10 +1785,10 @@
                 cb.onNonStrongBiometricAllowedChanged(userId);
             }
         }
-        if (userId == getCurrentUser()) {
+        if (userId == mSelectedUserInteractor.getSelectedUserId()) {
             FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
                     mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
-                            getCurrentUser()) ? -1 : 1);
+                            mSelectedUserInteractor.getSelectedUserId()) ? -1 : 1);
 
             // This is only reset when primary auth is used to enter the device, so we only check
             // whether to stop biometric listening states here
@@ -1979,6 +1986,7 @@
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
+                    mLogger.logFingerprintAcquired(acquireInfo);
                     handleFingerprintAcquired(acquireInfo);
                     Trace.endSection();
                 }
@@ -2074,7 +2082,6 @@
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
     private boolean mFaceLockedOutPermanent;
-    private final HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
 
     /**
      * When we receive a {@link android.content.Intent#ACTION_SIM_STATE_CHANGED} broadcast,
@@ -2185,12 +2192,12 @@
         }
 
         public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
-            int userId = getCurrentUser();
+            int userId = mSelectedUserInteractor.getSelectedUserId();
             return isBiometricAllowedForUser(isStrongBiometric, userId);
         }
 
         public boolean hasUserAuthenticatedSinceBoot() {
-            int userId = getCurrentUser();
+            int userId = mSelectedUserInteractor.getSelectedUserId();
             return (getStrongAuthForUser(userId)
                     & STRONG_AUTH_REQUIRED_AFTER_BOOT) == 0;
         }
@@ -2338,7 +2345,6 @@
             UserTracker userTracker,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
-            SecureSettings secureSettings,
             DumpManager dumpManager,
             @Background Executor backgroundExecutor,
             @Main Executor mainExecutor,
@@ -2370,7 +2376,8 @@
             TaskStackChangeListeners taskStackChangeListeners,
             IActivityTaskManager activityTaskManagerService,
             DisplayTracker displayTracker,
-            WakefulnessLifecycle wakefulness) {
+            WakefulnessLifecycle wakefulness,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2386,7 +2393,6 @@
         mStatusBarState = mStatusBarStateController.getState();
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
-        mSecureSettings = secureSettings;
         dumpManager.registerDumpable(this);
         mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2420,6 +2426,7 @@
         mWakefulness = wakefulness;
         mDisplayTracker = displayTracker;
         mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+        mSelectedUserInteractor = selectedUserInteractor;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2642,7 +2649,7 @@
 
         mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
         mIsSystemUser = mUserManager.isSystemUser();
-        int user = mUserTracker.getUserId();
+        int user = mSelectedUserInteractor.getSelectedUserId(true);
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
@@ -2692,16 +2699,6 @@
         }
     }
 
-    private void updateFaceEnrolled(int userId) {
-        final Boolean isFaceEnrolled = isFaceSupported()
-                && mBiometricEnabledForUser.get(userId)
-                && mAuthController.isFaceAuthEnrolled(userId);
-        if (mIsFaceEnrolled != isFaceEnrolled) {
-            mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled);
-        }
-        mIsFaceEnrolled = isFaceEnrolled;
-    }
-
     private boolean isFaceSupported() {
         return mFaceManager != null && !mFaceSensorProperties.isEmpty();
     }
@@ -2714,7 +2711,7 @@
      * @return true if there's at least one udfps enrolled for the current user.
      */
     public boolean isUdfpsEnrolled() {
-        return mAuthController.isUdfpsEnrolled(getCurrentUser());
+        return mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     /**
@@ -2729,7 +2726,7 @@
      * @return true if there's at least one sfps enrollment for the current user.
      */
     public boolean isSfpsEnrolled() {
-        return mAuthController.isSfpsEnrolled(getCurrentUser());
+        return mAuthController.isSfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     /**
@@ -2741,10 +2738,17 @@
     }
 
     /**
+     * @return true if there's at least one face enrolled for the given user.
+     */
+    public boolean isFaceEnrolled(int userId) {
+        return mAuthController.isFaceAuthEnrolled(userId);
+    }
+
+    /**
      * @return true if there's at least one face enrolled
      */
     public boolean isFaceEnrolled() {
-        return mIsFaceEnrolled;
+        return isFaceEnrolled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
@@ -2900,7 +2904,7 @@
 
         if (shouldTriggerActiveUnlock()) {
             mLogger.logActiveUnlockTriggered(reason);
-            mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
+            mTrustManager.reportUserMayRequestUnlock(mSelectedUserInteractor.getSelectedUserId());
         }
     }
 
@@ -2954,7 +2958,7 @@
 
         if (allowRequest && shouldTriggerActiveUnlock()) {
             mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
-            mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
+            mTrustManager.reportUserRequestedUnlock(mSelectedUserInteractor.getSelectedUserId(),
                     dismissKeyguard);
         }
     }
@@ -3025,7 +3029,7 @@
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
         // Gates:
-        final int user = getCurrentUser();
+        final int user = mSelectedUserInteractor.getSelectedUserId();
 
         // No need to trigger active unlock if we're already unlocked or don't have
         // pin/pattern/password setup
@@ -3067,30 +3071,33 @@
     }
 
     private boolean shouldListenForFingerprintAssistant() {
-        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(
+                mSelectedUserInteractor.getSelectedUserId());
         return mAssistantVisible && mKeyguardOccluded
                 && !(fingerprint != null && fingerprint.mAuthenticated)
-                && !mUserHasTrust.get(getCurrentUser(), false);
+                && !mUserHasTrust.get(
+                        mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
     private boolean shouldListenForFaceAssistant() {
-        BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(
+                mSelectedUserInteractor.getSelectedUserId());
         return mAssistantVisible
                 // There can be intermediate states where mKeyguardShowing is false but
                 // mKeyguardOccluded is true, we don't want to run face auth in such a scenario.
                 && (mKeyguardShowing && mKeyguardOccluded)
                 && !(face != null && face.mAuthenticated)
-                && !mUserHasTrust.get(getCurrentUser(), false);
+                && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
     private boolean shouldTriggerActiveUnlockForAssistant() {
         return mAssistantVisible && mKeyguardOccluded
-                && !mUserHasTrust.get(getCurrentUser(), false);
+                && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
     @VisibleForTesting
     protected boolean shouldListenForFingerprint(boolean isUdfps) {
-        final int user = getCurrentUser();
+        final int user = mSelectedUserInteractor.getSelectedUserId();
         final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
         final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
         final boolean shouldListenKeyguardState =
@@ -3179,7 +3186,7 @@
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
         final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
                 && !statusBarShadeLocked;
-        final int user = getCurrentUser();
+        final int user = mSelectedUserInteractor.getSelectedUserId();
         final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
         final boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
@@ -3279,7 +3286,7 @@
     }
 
     private void startListeningForFingerprint() {
-        final int userId = getCurrentUser();
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
             mLogger.logUnexpectedFpCancellationSignalState(
@@ -3326,7 +3333,7 @@
     }
 
     private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
-        final int userId = getCurrentUser();
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFacePossible(userId);
         if (mFaceCancelSignal != null) {
             mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
@@ -3430,48 +3437,22 @@
     }
 
     @SuppressLint("MissingPermission")
+    public boolean isUnlockWithFingerprintPossible(int userId) {
+        return isFingerprintSupported()
+                && !isFingerprintDisabled(userId) && mAuthController.isFingerprintEnrolled(userId);
+    }
+
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
     @VisibleForTesting
-    boolean isUnlockWithFingerprintPossible(int userId) {
-        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
-        boolean newFpEnrolled = isFingerprintSupported()
-                && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
-        Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
-        if (oldFpEnrolled != newFpEnrolled) {
-            mLogger.logFpEnrolledUpdated(userId, oldFpEnrolled, newFpEnrolled);
-        }
-        mIsUnlockWithFingerprintPossible.put(userId, newFpEnrolled);
-        return mIsUnlockWithFingerprintPossible.get(userId);
-    }
-
-    /**
-     * Cached value for whether fingerprint is enrolled and possible to use for authentication.
-     * Note: checking fingerprint enrollment directly with the AuthController requires an IPC.
-     */
-    public boolean getCachedIsUnlockWithFingerprintPossible(int userId) {
-        return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
-    }
-
-    /**
-     * @deprecated This is being migrated to use modern architecture.
-     */
     @Deprecated
-    private boolean isUnlockWithFacePossible(int userId) {
+    public boolean isUnlockWithFacePossible(int userId) {
         if (isFaceAuthInteractorEnabled()) {
-            return getFaceAuthInteractor().canFaceAuthRun();
+            return getFaceAuthInteractor() != null
+                    && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();
         }
-        return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
-    }
-
-    /**
-     * If face hardware is available, user has enrolled and enabled auth via setting.
-     *
-     * @deprecated This is being migrated to use modern architecture.
-     */
-    @Deprecated
-    public boolean isFaceAuthEnabledForUser(int userId) {
-        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
-        updateFaceEnrolled(userId);
-        return mIsFaceEnrolled;
+        return isFaceSupported() && isFaceEnrolled(userId) && !isFaceDisabled(userId);
     }
 
     private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) {
@@ -3868,12 +3849,12 @@
     }
 
     private boolean resolveNeedsSlowUnlockTransition() {
-        if (isUserUnlocked(getCurrentUser())) {
+        if (isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())) {
             return false;
         }
         Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
         ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent,
-                0 /* flags */, getCurrentUser());
+                0 /* flags */, mSelectedUserInteractor.getSelectedUserId());
 
         if (resolveInfo == null) {
             mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
@@ -4059,9 +4040,11 @@
     @AnyThread
     public void setSwitchingUser(boolean switching) {
         if (switching) {
-            mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser");
+            mLogger.logUserSwitching(
+                    mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser");
         } else {
-            mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser");
+            mLogger.logUserSwitchComplete(
+                    mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser");
         }
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post it first
@@ -4436,9 +4419,10 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
-        pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
+        pw.println("  getUserHasTrust()=" + getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
-                + getUserUnlockedWithBiometric(getCurrentUser()));
+                + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
@@ -4456,7 +4440,7 @@
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
         }
         if (isFingerprintSupported()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
@@ -4499,7 +4483,7 @@
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
         } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             pw.println("  Fingerprint state (user=" + userId + ")");
             pw.println("    mFingerprintSensorProperties.isEmpty="
                     + mFingerprintSensorProperties.isEmpty());
@@ -4513,7 +4497,7 @@
             ).printTableData(pw);
         }
         if (isFaceSupported()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
@@ -4543,7 +4527,7 @@
                     mFaceListenBuffer.toList()
             ).printTableData(pw);
         } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             pw.println("  Face state (user=" + userId + ")");
             pw.println("    mFaceSensorProperties.isEmpty="
                     + mFaceSensorProperties.isEmpty());
@@ -4557,7 +4541,7 @@
             ).printTableData(pw);
         }
         pw.println("ActiveUnlockRunning="
-                + mTrustManager.isActiveUnlockRunning(KeyguardUpdateMonitor.getCurrentUser()));
+                + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
         new DumpsysTableLogger(
                 "KeyguardActiveUnlockTriggers",
                 KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7b59632..2476067 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -25,12 +25,14 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
 import java.util.TimeZone;
 
 /**
  * Callback for general information relevant to lock screen.
  */
+@WeaklyReferencedCallback
 public class KeyguardUpdateMonitorCallback {
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index c64ae01..d524e4a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,6 +23,8 @@
 import android.view.View;
 
 import com.android.app.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
@@ -53,6 +55,7 @@
     private boolean mKeyguardViewVisibilityAnimating;
     private boolean mLastOccludedState = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
+    private final FeatureFlags mFeatureFlags;
     private final LogBuffer mLogBuffer;
 
     public KeyguardVisibilityHelper(View view,
@@ -60,12 +63,14 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             boolean animateYPos,
+            FeatureFlags featureFlags,
             LogBuffer logBuffer) {
         mView = view;
         mKeyguardStateController = keyguardStateController;
         mDozeParameters = dozeParameters;
         mScreenOffAnimationController = screenOffAnimationController;
         mAnimateYPos = animateYPos;
+        mFeatureFlags = featureFlags;
         mLogBuffer = logBuffer;
     }
 
@@ -162,13 +167,17 @@
                         animProps,
                         true /* animate */);
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
-                log("ScreenOff transition");
-                mKeyguardViewVisibilityAnimating = true;
+                if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                    log("Using GoneToAodTransition");
+                    mKeyguardViewVisibilityAnimating = false;
+                } else {
+                    log("ScreenOff transition");
+                    mKeyguardViewVisibilityAnimating = true;
 
-                // Ask the screen off animation controller to animate the keyguard visibility for us
-                // since it may need to be cancelled due to keyguard lifecycle events.
-                mScreenOffAnimationController.animateInKeyguard(
-                        mView, mSetVisibleEndRunnable);
+                    // Ask the screen off animation controller to animate the keyguard visibility
+                    // for us since it may need to be cancelled due to keyguard lifecycle events.
+                    mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable);
+                }
             } else {
                 log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 40d0be1..ff6a3d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -25,7 +25,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
@@ -105,18 +104,6 @@
         mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
     }
 
-    void setImageDrawable(Drawable drawable) {
-        mLockIcon.setImageDrawable(drawable);
-
-        if (!mUseBackground) return;
-
-        if (drawable == null) {
-            mBgView.setVisibility(View.INVISIBLE);
-        } else {
-            mBgView.setVisibility(View.VISIBLE);
-        }
-    }
-
     /**
      * Whether or not to render the lock icon background. Mainly used for UDPFS.
      */
@@ -197,6 +184,7 @@
         mLockIcon = new ImageView(context, attrs);
         mLockIcon.setId(R.id.lock_icon);
         mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon));
         addView(mLockIcon);
         LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams();
         lp.height = MATCH_PARENT;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 0d3f726..611283f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,7 +35,6 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Process;
@@ -120,9 +119,6 @@
     private boolean mUdfpsEnrolled;
     private Resources mResources;
     private Context mContext;
-
-    @NonNull private final AnimatedStateListDrawable mIcon;
-
     @NonNull private CharSequence mUnlockedLabel;
     @NonNull private CharSequence mLockedLabel;
     @NonNull private final VibratorHelper mVibrator;
@@ -147,7 +143,6 @@
     private boolean mCanDismissLockScreen;
     private int mStatusBarState;
     private boolean mIsKeyguardShowing;
-    private Runnable mOnGestureDetectedRunnable;
     private Runnable mLongPressCancelRunnable;
 
     private boolean mUdfpsSupported;
@@ -232,9 +227,6 @@
 
         mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
         mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
-
-        mIcon = (AnimatedStateListDrawable)
-                resources.getDrawable(R.drawable.super_lock_icon, context.getTheme());
         mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
         mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
         mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress);
@@ -270,7 +262,6 @@
     @SuppressLint("ClickableViewAccessibility")
     public void setLockIconView(LockIconView lockIconView) {
         mView = lockIconView;
-        mView.setImageDrawable(mIcon);
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
 
         if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
@@ -492,10 +483,6 @@
         pw.println("mUdfpsSupported: " + mUdfpsSupported);
         pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
         pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
-        pw.println(" mIcon: ");
-        for (int state : mIcon.getState()) {
-            pw.print(" " + state);
-        }
         pw.println();
         pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
         pw.println(" mShowLockIcon: " + mShowLockIcon);
@@ -692,10 +679,10 @@
                 mVelocityTracker.addMovement(event);
                 // Compute pointer velocity in pixels per second.
                 mVelocityTracker.computeCurrentVelocity(1000);
-                float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
+                float velocity = computePointerSpeed(mVelocityTracker,
                         mActivePointerId);
                 if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
-                        && UdfpsController.exceedsVelocityThreshold(velocity)) {
+                        && exceedsVelocityThreshold(velocity)) {
                     Log.v(TAG, "lock icon long-press rescheduled due to "
                             + "high pointer velocity=" + velocity);
                     mLongPressCancelRunnable.run();
@@ -713,6 +700,23 @@
         return true;
     }
 
+    /**
+     * Calculate the pointer speed given a velocity tracker and the pointer id.
+     * This assumes that the velocity tracker has already been passed all relevant motion events.
+     */
+    private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
+        final float vx = tracker.getXVelocity(pointerId);
+        final float vy = tracker.getYVelocity(pointerId);
+        return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
+    }
+
+    /**
+     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
+     */
+    private static boolean exceedsVelocityThreshold(float velocity) {
+        return velocity > 750f;
+    }
+
     private boolean actionableDownEventStartedOnView(MotionEvent event) {
         if (!isActionable()) {
             return false;
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt
index c581788..1f145d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt
@@ -4,6 +4,7 @@
 import android.content.res.Resources
 import android.view.Display
 import com.android.systemui.dagger.qualifiers.DisplaySpecific
+import com.android.systemui.util.kotlin.getOrNull
 import dagger.BindsOptionalOf
 import dagger.Module
 import dagger.Provides
@@ -23,11 +24,12 @@
     companion object {
         @Provides
         @DisplaySpecific
-        fun getDisplayContext(context: Context, display: Optional<Display>): Context {
-            return if (display.isPresent) {
-                context.createDisplayContext(display.get())
-            } else {
+        fun getDisplayContext(context: Context, optionalDisplay: Optional<Display>): Context {
+            val display = optionalDisplay.getOrNull() ?: return context
+            return if (context.displayId == display.displayId) {
                 context
+            } else {
+                context.createDisplayContext(display)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2dfb370..5bf8d63 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -44,6 +44,7 @@
 import javax.inject.Inject
 
 private const val TAG = "KeyguardUpdateMonitorLog"
+private const val FP_LOG_TAG = "KeyguardFingerprintLog"
 
 /** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */
 class KeyguardUpdateMonitorLogger
@@ -157,7 +158,7 @@
 
     fun logFingerprintAuthForWrongUser(authUserId: Int) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             { int1 = authUserId },
             { "Fingerprint authenticated for wrong user: $int1" }
@@ -166,7 +167,7 @@
 
     fun logFingerprintDisabledForUser(userId: Int) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             { int1 = userId },
             { "Fingerprint disabled by DPM for userId: $int1" }
@@ -174,12 +175,17 @@
     }
 
     fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
-        logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+        logBuffer.log(
+            FP_LOG_TAG,
+            DEBUG,
+            { int1 = mode },
+            { "handleFingerprintLockoutReset: $int1" }
+        )
     }
 
     fun logFingerprintRunningState(fingerprintRunningState: Int) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             { int1 = fingerprintRunningState },
             { "fingerprintRunningState: $int1" }
@@ -188,7 +194,7 @@
 
     fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             {
                 int1 = userId
@@ -212,7 +218,7 @@
 
     fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             {
                 int1 = userId
@@ -224,7 +230,7 @@
 
     fun logFingerprintError(msgId: Int, originalErrMsg: String) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             {
                 str1 = originalErrMsg
@@ -451,7 +457,7 @@
     }
 
     fun logSubInfo(subInfo: SubscriptionInfo?) {
-        logBuffer.log(TAG, VERBOSE, { str1 = "$subInfo" }, { "SubInfo:$str1" })
+        logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" })
     }
 
     fun logTimeFormatChanged(newTimeFormat: String?) {
@@ -654,19 +660,6 @@
         )
     }
 
-    fun logFpEnrolledUpdated(userId: Int, oldValue: Boolean, newValue: Boolean) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            {
-                int1 = userId
-                bool1 = oldValue
-                bool2 = newValue
-            },
-            { "Fp enrolled state changed for userId: $int1 old: $bool1, new: $bool2" }
-        )
-    }
-
     fun logTrustUsuallyManagedUpdated(
         userId: Int,
         oldValue: Boolean,
@@ -751,4 +744,25 @@
             { "userSwitchComplete: $str1, userId: $int1" }
         )
     }
+
+    fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) {
+        logBuffer.log(
+            FP_LOG_TAG,
+            DEBUG,
+            {
+                int1 = helpMsgId
+                str1 = "$helpString"
+            },
+            { "fingerprint help message: $int1, $str1" }
+        )
+    }
+
+    fun logFingerprintAcquired(acquireInfo: Int) {
+        logBuffer.log(
+            FP_LOG_TAG,
+            DEBUG,
+            { int1 = acquireInfo },
+            { "fingerprint acquire message: $int1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 2d2ebe9..d33d279 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,6 +17,7 @@
 package com.android.systemui
 
 import android.content.Context
+import android.content.res.Resources
 import android.graphics.Path
 import android.graphics.Rect
 import android.graphics.RectF
@@ -33,37 +34,32 @@
  */
 class CameraAvailabilityListener(
     private val cameraManager: CameraManager,
-    private val cutoutProtectionPath: Path,
-    private val targetCameraId: String,
+    private val cameraProtectionInfoList: List<CameraProtectionInfo>,
     excludedPackages: String,
     private val executor: Executor
 ) {
-    private var cutoutBounds = Rect()
     private val excludedPackageIds: Set<String>
     private val listeners = mutableListOf<CameraTransitionCallback>()
     private val availabilityCallback: CameraManager.AvailabilityCallback =
             object : CameraManager.AvailabilityCallback() {
                 override fun onCameraClosed(cameraId: String) {
-                    if (targetCameraId == cameraId) {
-                        notifyCameraInactive()
+                    cameraProtectionInfoList.forEach {
+                        if (cameraId == it.cameraId) {
+                            notifyCameraInactive()
+                        }
                     }
                 }
 
                 override fun onCameraOpened(cameraId: String, packageId: String) {
-                    if (targetCameraId == cameraId && !isExcluded(packageId)) {
-                        notifyCameraActive()
+                    cameraProtectionInfoList.forEach {
+                        if (cameraId == it.cameraId && !isExcluded(packageId)) {
+                            notifyCameraActive(it)
+                        }
                     }
                 }
     }
 
     init {
-        val computed = RectF()
-        cutoutProtectionPath.computeBounds(computed, false /* unused */)
-        cutoutBounds.set(
-                computed.left.roundToInt(),
-                computed.top.roundToInt(),
-                computed.right.roundToInt(),
-                computed.bottom.roundToInt())
         excludedPackageIds = excludedPackages.split(",").toSet()
     }
 
@@ -100,8 +96,10 @@
         cameraManager.unregisterAvailabilityCallback(availabilityCallback)
     }
 
-    private fun notifyCameraActive() {
-        listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
+    private fun notifyCameraActive(info: CameraProtectionInfo) {
+        listeners.forEach {
+            it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+        }
     }
 
     private fun notifyCameraInactive() {
@@ -121,12 +119,11 @@
             val manager = context
                     .getSystemService(Context.CAMERA_SERVICE) as CameraManager
             val res = context.resources
-            val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
-            val cameraId = res.getString(R.string.config_protectedCameraId)
+            val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
             val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
 
             return CameraAvailabilityListener(
-                    manager, pathFromString(pathString), cameraId, excluded, executor)
+                    manager, cameraProtectionInfoList, excluded, executor)
         }
 
         private fun pathFromString(pathString: String): Path {
@@ -140,5 +137,53 @@
 
             return p
         }
+
+        private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
+            val list = mutableListOf<CameraProtectionInfo>()
+            val front = loadCameraProtectionInfo(
+                    res,
+                    R.string.config_protectedCameraId,
+                    R.string.config_frontBuiltInDisplayCutoutProtection
+            )
+            if (front != null) {
+                list.add(front)
+            }
+            val inner = loadCameraProtectionInfo(
+                    res,
+                    R.string.config_protectedInnerCameraId,
+                    R.string.config_innerBuiltInDisplayCutoutProtection
+            )
+            if (inner != null) {
+                list.add(inner)
+            }
+            return list
+        }
+
+        private fun loadCameraProtectionInfo(
+                res: Resources,
+                cameraIdRes: Int,
+                pathRes: Int
+        ): CameraProtectionInfo? {
+            val cameraId = res.getString(cameraIdRes)
+            if (cameraId == null || cameraId.isEmpty()) {
+                return null
+            }
+            val protectionPath = pathFromString(res.getString(pathRes))
+            val computed = RectF()
+            protectionPath.computeBounds(computed)
+            val protectionBounds = Rect(
+                    computed.left.roundToInt(),
+                    computed.top.roundToInt(),
+                    computed.right.roundToInt(),
+                    computed.bottom.roundToInt()
+            )
+            return CameraProtectionInfo(cameraId, protectionPath, protectionBounds)
+        }
     }
+
+    data class CameraProtectionInfo (
+            val cameraId: String,
+            val cutoutProtectionPath: Path,
+            val cutoutBounds: Rect
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 7739021..1a34cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,123 +15,55 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.INotificationManager;
-import android.app.IWallpaperManager;
-import android.hardware.SensorPrivacyManager;
-import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.ArrayMap;
-import android.util.DisplayMetrics;
-import android.view.IWindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.Preconditions;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
-import com.android.systemui.power.PowerUI;
-import com.android.systemui.privacy.PrivacyItemController;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 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.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
-import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.policy.AccessibilityController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.SensorPrivacyController;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.leak.GarbageMonitor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.leak.LeakReporter;
-import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import dagger.Lazy;
 
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -154,10 +86,6 @@
  */
 @SysUISingleton
 public class Dependency {
-    /**
-     * Key for getting a the main looper.
-     */
-    private static final String MAIN_LOOPER_NAME = "main_looper";
 
     /**
      * Key for getting a background Looper for background work.
@@ -171,15 +99,6 @@
      * Generic handler on the main thread.
      */
     private static final String MAIN_HANDLER_NAME = "main_handler";
-    /**
-     * Generic executor on the main thread.
-     */
-    private static final String MAIN_EXECUTOR_NAME = "main_executor";
-
-    /**
-     * Generic executor on a background thread.
-     */
-    private static final String BACKGROUND_EXECUTOR_NAME = "background_executor";
 
     /**
      * An email address to send memory leak reports to by default.
@@ -197,10 +116,6 @@
      */
     public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME);
     /**
-     * Key for getting a mainer Looper.
-     */
-    public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME);
-    /**
      * Key for getting a Handler for receiving time tick broadcasts on.
      */
     public static final DependencyKey<Handler> TIME_TICK_HANDLER =
@@ -211,133 +126,43 @@
     public static final DependencyKey<Handler> MAIN_HANDLER =
             new DependencyKey<>(MAIN_HANDLER_NAME);
 
-    /**
-     * Generic executor on the main thread.
-     */
-    public static final DependencyKey<Executor> MAIN_EXECUTOR =
-            new DependencyKey<>(MAIN_EXECUTOR_NAME);
-    /**
-     * Generic executor on a background thread.
-     */
-    public static final DependencyKey<Executor> BACKGROUND_EXECUTOR =
-            new DependencyKey<>(BACKGROUND_EXECUTOR_NAME);
-
-    /**
-     * An email address to send memory leak reports to by default.
-     */
-    public static final DependencyKey<String> LEAK_REPORT_EMAIL =
-            new DependencyKey<>(LEAK_REPORT_EMAIL_NAME);
-
     private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
     private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
 
     @Inject DumpManager mDumpManager;
 
-    @Inject Lazy<ActivityStarter> mActivityStarter;
     @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
-    @Inject Lazy<AsyncSensorManager> mAsyncSensorManager;
     @Inject Lazy<BluetoothController> mBluetoothController;
-    @Inject Lazy<LocationController> mLocationController;
-    @Inject Lazy<RotationLockController> mRotationLockController;
-    @Inject Lazy<ZenModeController> mZenModeController;
-    @Inject Lazy<HotspotController> mHotspotController;
-    @Inject Lazy<CastController> mCastController;
     @Inject Lazy<FlashlightController> mFlashlightController;
-    @Inject Lazy<UserSwitcherController> mUserSwitcherController;
-    @Inject Lazy<UserInfoController> mUserInfoController;
-    @Inject Lazy<KeyguardStateController> mKeyguardMonitor;
     @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
-    @Inject Lazy<NightDisplayListener> mNightDisplayListener;
-    @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController;
-    @Inject Lazy<ManagedProfileController> mManagedProfileController;
-    @Inject Lazy<NextAlarmController> mNextAlarmController;
-    @Inject Lazy<DataSaverController> mDataSaverController;
-    @Inject Lazy<AccessibilityController> mAccessibilityController;
     @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController;
     @Inject Lazy<PluginManager> mPluginManager;
     @Inject Lazy<AssistManager> mAssistManager;
-    @Inject Lazy<SecurityController> mSecurityController;
-    @Inject Lazy<LeakDetector> mLeakDetector;
-    @Inject Lazy<LeakReporter> mLeakReporter;
-    @Inject Lazy<GarbageMonitor> mGarbageMonitor;
     @Inject Lazy<TunerService> mTunerService;
-    @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
-    @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController;
     @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
-    @Inject Lazy<StatusBarIconController> mStatusBarIconController;
-    @Inject Lazy<ScreenLifecycle> mScreenLifecycle;
-    @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
     @Inject Lazy<FragmentService> mFragmentService;
-    @Inject Lazy<ExtensionController> mExtensionController;
-    @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider;
     @Nullable
-    @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager;
     @Inject Lazy<VolumeDialogController> mVolumeDialogController;
     @Inject Lazy<MetricsLogger> mMetricsLogger;
-    @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
-    @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
     @Inject Lazy<TunablePaddingService> mTunablePaddingService;
     @Inject Lazy<UiOffloadThread> mUiOffloadThread;
-    @Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
     @Inject Lazy<LightBarController> mLightBarController;
-    @Inject Lazy<IWindowManager> mIWindowManager;
     @Inject Lazy<OverviewProxyService> mOverviewProxyService;
     @Inject Lazy<NavigationModeController> mNavBarModeController;
     @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
     @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
-    @Inject Lazy<EnhancedEstimates> mEnhancedEstimates;
-    @Inject Lazy<VibratorHelper> mVibratorHelper;
     @Inject Lazy<IStatusBarService> mIStatusBarService;
-    @Inject Lazy<DisplayMetrics> mDisplayMetrics;
-    @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger;
-    @Inject Lazy<ShadeController> mShadeController;
     @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
-    @Inject Lazy<AppOpsController> mAppOpsController;
     @Inject Lazy<NavigationBarController> mNavigationBarController;
-    @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
-    @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
     @Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
-    @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager;
-    @Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
-    @Inject Lazy<NotificationListener> mNotificationListener;
-    @Inject Lazy<NotificationLogger> mNotificationLogger;
-    @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
-    @Inject Lazy<SmartReplyController> mSmartReplyController;
-    @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
-    @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
-    @Inject Lazy<AutoHideController> mAutoHideController;
-    @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Background Lazy<Looper> mBgLooper;
-    @Inject @Background Lazy<Handler> mBgHandler;
-    @Inject @Main Lazy<Looper> mMainLooper;
     @Inject @Main Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
-    @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
-    @Inject @Main Lazy<Executor> mMainExecutor;
-    @Inject @Background Lazy<Executor> mBackgroundExecutor;
-    @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
-    @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
-    @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
-    @Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
-    @Inject Lazy<DockManager> mDockManager;
-    @Inject Lazy<INotificationManager> mINotificationManager;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
-    @Inject Lazy<AlarmManager> mAlarmManager;
-    @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel;
-    @Inject Lazy<DozeParameters> mDozeParameters;
-    @Inject Lazy<IWallpaperManager> mWallpaperManager;
     @Inject Lazy<CommandQueue> mCommandQueue;
-    @Inject Lazy<RecordingController> mRecordingController;
-    @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
-    @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
-    @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
-    @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy;
-    @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy;
-    @Inject Lazy<EdgeBackGestureHandler.Factory> mEdgeBackGestureHandlerFactoryLazy;
     @Inject Lazy<UiEventLogger> mUiEventLogger;
     @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
-    @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
     @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
     @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController;
@@ -360,183 +185,36 @@
         // on imports.
         mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
         mProviders.put(BG_LOOPER, mBgLooper::get);
-        mProviders.put(MAIN_LOOPER, mMainLooper::get);
         mProviders.put(MAIN_HANDLER, mMainHandler::get);
-        mProviders.put(MAIN_EXECUTOR, mMainExecutor::get);
-        mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get);
-        mProviders.put(ActivityStarter.class, mActivityStarter::get);
         mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
-
-        mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get);
-
         mProviders.put(BluetoothController.class, mBluetoothController::get);
-        mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get);
-
-        mProviders.put(LocationController.class, mLocationController::get);
-
-        mProviders.put(RotationLockController.class, mRotationLockController::get);
-
-        mProviders.put(ZenModeController.class, mZenModeController::get);
-
-        mProviders.put(HotspotController.class, mHotspotController::get);
-
-        mProviders.put(CastController.class, mCastController::get);
-
         mProviders.put(FlashlightController.class, mFlashlightController::get);
-
-        mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get);
-
         mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
-
-        mProviders.put(UserSwitcherController.class, mUserSwitcherController::get);
-
-        mProviders.put(UserInfoController.class, mUserInfoController::get);
-
-        mProviders.put(NightDisplayListener.class, mNightDisplayListener::get);
-
-        mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get);
-
-        mProviders.put(ManagedProfileController.class, mManagedProfileController::get);
-
-        mProviders.put(NextAlarmController.class, mNextAlarmController::get);
-
-        mProviders.put(DataSaverController.class, mDataSaverController::get);
-
-        mProviders.put(AccessibilityController.class, mAccessibilityController::get);
-
         mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get);
-
         mProviders.put(PluginManager.class, mPluginManager::get);
-
         mProviders.put(AssistManager.class, mAssistManager::get);
-
-        mProviders.put(SecurityController.class, mSecurityController::get);
-
-        mProviders.put(LeakDetector.class, mLeakDetector::get);
-
-        mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get);
-
-        mProviders.put(LeakReporter.class, mLeakReporter::get);
-
-        mProviders.put(GarbageMonitor.class, mGarbageMonitor::get);
-
         mProviders.put(TunerService.class, mTunerService::get);
-
-        mProviders.put(NotificationShadeWindowController.class,
-                mNotificationShadeWindowController::get);
-
-        mProviders.put(StatusBarWindowController.class, mTempStatusBarWindowController::get);
-
         mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get);
-
-        mProviders.put(StatusBarIconController.class, mStatusBarIconController::get);
-
-        mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get);
-
-        mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get);
-
         mProviders.put(FragmentService.class, mFragmentService::get);
-
-        mProviders.put(ExtensionController.class, mExtensionController::get);
-
-        mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get);
-
-        mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get);
-
         mProviders.put(VolumeDialogController.class, mVolumeDialogController::get);
-
         mProviders.put(MetricsLogger.class, mMetricsLogger::get);
-
-        mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get);
-
-        mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get);
-
         mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
-
         mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
-
-        mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
-
         mProviders.put(LightBarController.class, mLightBarController::get);
-
-        mProviders.put(IWindowManager.class, mIWindowManager::get);
-
         mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
-
         mProviders.put(NavigationModeController.class, mNavBarModeController::get);
-
         mProviders.put(AccessibilityButtonModeObserver.class,
                 mAccessibilityButtonModeObserver::get);
         mProviders.put(AccessibilityButtonTargetsObserver.class,
                 mAccessibilityButtonListController::get);
-
-        mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get);
-
-        mProviders.put(VibratorHelper.class, mVibratorHelper::get);
-
         mProviders.put(IStatusBarService.class, mIStatusBarService::get);
-
-        mProviders.put(DisplayMetrics.class, mDisplayMetrics::get);
-
-        mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get);
-
-        mProviders.put(ShadeController.class, mShadeController::get);
-
         mProviders.put(NotificationRemoteInputManager.Callback.class,
                 mNotificationRemoteInputManagerCallback::get);
-
-        mProviders.put(AppOpsController.class, mAppOpsController::get);
-
         mProviders.put(NavigationBarController.class, mNavigationBarController::get);
-
-        mProviders.put(AccessibilityFloatingMenuController.class,
-                mAccessibilityFloatingMenuController::get);
-
         mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
-        mProviders.put(NotificationLockscreenUserManager.class,
-                mNotificationLockscreenUserManager::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
-        mProviders.put(NotificationRemoteInputManager.class,
-                mNotificationRemoteInputManager::get);
-        mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
-        mProviders.put(NotificationListener.class, mNotificationListener::get);
-        mProviders.put(NotificationLogger.class, mNotificationLogger::get);
-        mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
-        mProviders.put(SmartReplyController.class, mSmartReplyController::get);
-        mProviders.put(RemoteInputQuickSettingsDisabler.class,
-                mRemoteInputQuickSettingsDisabler::get);
-        mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
-        mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
-        mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
-        mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
-        mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get);
-        mProviders.put(DockManager.class, mDockManager::get);
-        mProviders.put(INotificationManager.class, mINotificationManager::get);
         mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
-        mProviders.put(AlarmManager.class, mAlarmManager::get);
-        mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get);
-        mProviders.put(DozeParameters.class, mDozeParameters::get);
-        mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
         mProviders.put(CommandQueue.class, mCommandQueue::get);
-        mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
-        mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
-
-        // TODO(b/118592525): to support multi-display , we start to add something which is
-        //                    per-display, while others may be global. I think it's time to add
-        //                    a new class maybe named DisplayDependency to solve per-display
-        //                    Dependency problem.
-        mProviders.put(AutoHideController.class, mAutoHideController::get);
-
-        mProviders.put(RecordingController.class, mRecordingController::get);
-
-        mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get);
-
-        mProviders.put(SystemStatusAnimationScheduler.class,
-                mSystemStatusAnimationSchedulerLazy::get);
-        mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
-        mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get);
-        mProviders.put(EdgeBackGestureHandler.Factory.class,
-                mEdgeBackGestureHandlerFactoryLazy::get);
         mProviders.put(UiEventLogger.class, mUiEventLogger::get);
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
         mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
index fd84543..494efb7 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
@@ -25,21 +25,24 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.os.UserHandle;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
-import javax.inject.Inject;
-
+import dagger.Lazy;
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import javax.inject.Inject;
+
 /**
  * Manages handling of guest session persistent notification
  * and actions to reset guest or exit guest session
@@ -70,14 +73,14 @@
     public AlertDialog mResetSessionDialog;
     private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final ResetSessionDialog.Factory mResetSessionDialogFactory;
-    private final ExitSessionDialog.Factory mExitSessionDialogFactory;
+    private final ResetSessionDialogFactory mResetSessionDialogFactory;
+    private final ExitSessionDialogFactory mExitSessionDialogFactory;
 
     @Inject
     public GuestResetOrExitSessionReceiver(UserTracker userTracker,
             BroadcastDispatcher broadcastDispatcher,
-            ResetSessionDialog.Factory resetSessionDialogFactory,
-            ExitSessionDialog.Factory exitSessionDialogFactory) {
+            ResetSessionDialogFactory resetSessionDialogFactory,
+            ExitSessionDialogFactory exitSessionDialogFactory) {
         mUserTracker = userTracker;
         mBroadcastDispatcher = broadcastDispatcher;
         mResetSessionDialogFactory = resetSessionDialogFactory;
@@ -111,8 +114,8 @@
             mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
             mResetSessionDialog.show();
         } else if (ACTION_GUEST_EXIT.equals(action)) {
-            mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id,
-                        currentUser.isEphemeral());
+            mExitSessionDialog = mExitSessionDialogFactory.create(
+                    currentUser.isEphemeral(), currentUser.id);
             mExitSessionDialog.show();
         }
     }
@@ -132,43 +135,69 @@
     }
 
     /**
+     * Factory class to create guest reset dialog instance
+     *
      * Dialog shown when asking for confirmation before
      * reset and restart of guest user.
      */
-    public static final class ResetSessionDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
+    public static final class ResetSessionDialogFactory {
+        private final Lazy<SystemUIDialog> mDialogLazy;
+        private final Resources mResources;
+        private final ResetSessionDialogClickListener.Factory mClickListenerFactory;
 
+        @Inject
+        public ResetSessionDialogFactory(
+                Lazy<SystemUIDialog> dialogLazy,
+                @Main Resources resources,
+                ResetSessionDialogClickListener.Factory clickListenerFactory) {
+            mDialogLazy = dialogLazy;
+            mResources = resources;
+            mClickListenerFactory = clickListenerFactory;
+        }
+
+        /** Create a guest reset dialog instance */
+        public AlertDialog create(int userId) {
+            SystemUIDialog dialog = mDialogLazy.get();
+            ResetSessionDialogClickListener listener = mClickListenerFactory.create(
+                    userId, dialog);
+            dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
+            dialog.setMessage(mResources.getString(
+                    com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
+            dialog.setButton(
+                    DialogInterface.BUTTON_NEUTRAL,
+                    mResources.getString(android.R.string.cancel),
+                    listener);
+            dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                    mResources.getString(
+                            com.android.settingslib.R.string.guest_reset_guest_confirm_button),
+                    listener);
+            dialog.setCanceledOnTouchOutside(false);
+            return dialog;
+        }
+    }
+
+    public static class ResetSessionDialogClickListener implements DialogInterface.OnClickListener {
         private final UserSwitcherController mUserSwitcherController;
         private final UiEventLogger mUiEventLogger;
         private final int mUserId;
+        private final DialogInterface mDialog;
 
-        /** Factory class to create guest reset dialog instance */
         @AssistedFactory
         public interface Factory {
-            /** Create a guest reset dialog instance */
-            ResetSessionDialog create(int userId);
+            ResetSessionDialogClickListener create(int userId, DialogInterface dialog);
         }
 
         @AssistedInject
-        ResetSessionDialog(Context context,
+        public ResetSessionDialogClickListener(
                 UserSwitcherController userSwitcherController,
                 UiEventLogger uiEventLogger,
-                @Assisted int userId) {
-            super(context);
-
-            setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
-            setMessage(context.getString(
-                        com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
-            setButton(DialogInterface.BUTTON_NEUTRAL,
-                    context.getString(android.R.string.cancel), this);
-            setButton(DialogInterface.BUTTON_POSITIVE,
-                    context.getString(
-                        com.android.settingslib.R.string.guest_reset_guest_confirm_button), this);
-            setCanceledOnTouchOutside(false);
-
+                @Assisted int userId,
+                @Assisted DialogInterface dialog
+        ) {
             mUserSwitcherController = userSwitcherController;
             mUiEventLogger = uiEventLogger;
             mUserId = userId;
+            mDialog = dialog;
         }
 
         @Override
@@ -177,7 +206,7 @@
                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
                 mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
             } else if (which == DialogInterface.BUTTON_NEUTRAL) {
-                cancel();
+                mDialog.cancel();
             }
         }
     }
@@ -186,58 +215,93 @@
      * Dialog shown when asking for confirmation before
      * exit of guest user.
      */
-    public static final class ExitSessionDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
+    public static final class ExitSessionDialogFactory {
+        private final Lazy<SystemUIDialog> mDialogLazy;
+        private final ExitSessionDialogClickListener.Factory mClickListenerFactory;
+        private final Resources mResources;
 
+        @Inject
+        public ExitSessionDialogFactory(
+                Lazy<SystemUIDialog> dialogLazy,
+                ExitSessionDialogClickListener.Factory clickListenerFactory,
+                @Main Resources resources) {
+            mDialogLazy = dialogLazy;
+            mClickListenerFactory = clickListenerFactory;
+            mResources = resources;
+        }
+
+        public AlertDialog create(boolean isEphemeral, int userId) {
+            SystemUIDialog dialog = mDialogLazy.get();
+            ExitSessionDialogClickListener clickListener = mClickListenerFactory.create(
+                    isEphemeral, userId, dialog);
+            if (isEphemeral) {
+                dialog.setTitle(mResources.getString(
+                        com.android.settingslib.R.string.guest_exit_dialog_title));
+                dialog.setMessage(mResources.getString(
+                        com.android.settingslib.R.string.guest_exit_dialog_message));
+                dialog.setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        mResources.getString(android.R.string.cancel),
+                        clickListener);
+                dialog.setButton(
+                        DialogInterface.BUTTON_POSITIVE,
+                        mResources.getString(
+                                com.android.settingslib.R.string.guest_exit_dialog_button),
+                        clickListener);
+            } else {
+                dialog.setTitle(mResources.getString(
+                        com.android.settingslib
+                                .R.string.guest_exit_dialog_title_non_ephemeral));
+                dialog.setMessage(mResources.getString(
+                        com.android.settingslib
+                                .R.string.guest_exit_dialog_message_non_ephemeral));
+                dialog.setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        mResources.getString(android.R.string.cancel),
+                        clickListener);
+                dialog.setButton(
+                        DialogInterface.BUTTON_NEGATIVE,
+                        mResources.getString(
+                                com.android.settingslib.R.string.guest_exit_clear_data_button),
+                        clickListener);
+                dialog.setButton(
+                        DialogInterface.BUTTON_POSITIVE,
+                        mResources.getString(
+                                com.android.settingslib.R.string.guest_exit_save_data_button),
+                        clickListener);
+            }
+            dialog.setCanceledOnTouchOutside(false);
+
+            return dialog;
+        }
+
+    }
+
+    public static class ExitSessionDialogClickListener implements DialogInterface.OnClickListener {
         private final UserSwitcherController mUserSwitcherController;
+        private final boolean mIsEphemeral;
         private final int mUserId;
-        private boolean mIsEphemeral;
+        private final DialogInterface mDialog;
 
-        /** Factory class to create guest exit dialog instance */
         @AssistedFactory
         public interface Factory {
-            /** Create a guest exit dialog instance */
-            ExitSessionDialog create(int userId, boolean isEphemeral);
+            ExitSessionDialogClickListener create(
+                    boolean isEphemeral,
+                    int userId,
+                    DialogInterface dialog);
         }
 
         @AssistedInject
-        ExitSessionDialog(Context context,
+        public ExitSessionDialogClickListener(
                 UserSwitcherController userSwitcherController,
+                @Assisted boolean isEphemeral,
                 @Assisted int userId,
-                @Assisted boolean isEphemeral) {
-            super(context);
-
-            if (isEphemeral) {
-                setTitle(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_title));
-                setMessage(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_message));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_button), this);
-            } else {
-                setTitle(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_title_non_ephemeral));
-                setMessage(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_message_non_ephemeral));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_NEGATIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_clear_data_button), this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_save_data_button), this);
-            }
-            setCanceledOnTouchOutside(false);
-
+                @Assisted DialogInterface dialog
+        ) {
             mUserSwitcherController = userSwitcherController;
-            mUserId = userId;
             mIsEphemeral = isEphemeral;
+            mUserId = userId;
+            mDialog = dialog;
         }
 
         @Override
@@ -249,7 +313,7 @@
                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
                 } else if (which == DialogInterface.BUTTON_NEUTRAL) {
                     // Cancel clicked, do nothing
-                    cancel();
+                    mDialog.cancel();
                 }
             } else {
                 if (which == DialogInterface.BUTTON_POSITIVE) {
@@ -261,7 +325,7 @@
                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
                 } else if (which == DialogInterface.BUTTON_NEUTRAL) {
                     // Cancel clicked, do nothing
-                    cancel();
+                    mDialog.cancel();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index b573fad..0f5f869 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -27,6 +27,7 @@
 import com.android.systemui.res.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -36,14 +37,14 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
 /**
  * Manages notification when a guest session is resumed.
  */
@@ -58,7 +59,7 @@
     private final Executor mMainExecutor;
     private final UserTracker mUserTracker;
     private final SecureSettings mSecureSettings;
-    private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+    private final ResetSessionDialogFactory mResetSessionDialogFactory;
     private final GuestSessionNotification mGuestSessionNotification;
 
     @VisibleForTesting
@@ -104,7 +105,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings,
             GuestSessionNotification guestSessionNotification,
-            ResetSessionDialog.Factory resetSessionDialogFactory) {
+            ResetSessionDialogFactory resetSessionDialogFactory) {
         mMainExecutor = mainExecutor;
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index b33d501..c860979 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -31,7 +31,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -53,25 +53,25 @@
     private static final String
             ACTION_FACE_WAKE =
             "com.android.systemui.latency.ACTION_FACE_WAKE";
-    private final BiometricUnlockController mBiometricUnlockController;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private boolean mEnabled;
 
     @Inject
     public LatencyTester(
-            BiometricUnlockController biometricUnlockController,
             BroadcastDispatcher broadcastDispatcher,
             DeviceConfigProxy deviceConfigProxy,
             @Main DelayableExecutor mainExecutor,
-            KeyguardUpdateMonitor keyguardUpdateMonitor
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SelectedUserInteractor selectedUserInteractor
     ) {
-        mBiometricUnlockController = biometricUnlockController;
         mBroadcastDispatcher = broadcastDispatcher;
         mDeviceConfigProxy = deviceConfigProxy;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         updateEnabled();
         mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
@@ -87,11 +87,11 @@
             return;
         }
         if (type == BiometricSourceType.FACE) {
-            mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+            mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
                     true);
         } else if (type == BiometricSourceType.FINGERPRINT) {
             mKeyguardUpdateMonitor.onFingerprintAuthenticated(
-                    KeyguardUpdateMonitor.getCurrentUser(), true);
+                    mSelectedUserInteractor.getSelectedUserId(), true);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 54dbf72..6faee8c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -83,7 +83,7 @@
 import com.android.systemui.decor.RoundedCornerResDelegateImpl;
 import com.android.systemui.decor.ScreenDecorCommand;
 import com.android.systemui.log.ScreenDecorationsLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -165,7 +165,7 @@
     ScreenDecorHwcLayer mScreenDecorHwcLayer;
     private WindowManager mWindowManager;
     private int mRotation;
-    private SettingObserver mColorInversionSetting;
+    private UserSettingObserver mColorInversionSetting;
     @Nullable
     private DelayableExecutor mExecutor;
     private Handler mHandler;
@@ -686,7 +686,7 @@
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
-                mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler,
+                mColorInversionSetting = new UserSettingObserver(mSecureSettings, mHandler,
                         Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
                         mUserTracker.getUserId()) {
                     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 584357b..0bd4859 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1461,8 +1461,7 @@
         mDragView.setColorFilter(filter);
     }
 
-    @VisibleForTesting
-    void setBounceEffectDuration(int duration) {
+    private void setBounceEffectDuration(int duration) {
         mBounceEffectDuration = duration;
     }
 
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 105de16c..cd8bef1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.systemui.Flags;
 
 import java.util.HashMap;
 
@@ -91,16 +92,48 @@
     }
 
     void moveToPosition(PointF position) {
-        moveToPositionX(position.x);
-        moveToPositionY(position.y);
+        moveToPosition(position, /* animateMovement = */ false);
+    }
+
+    /* Moves position without updating underlying percentage position. Can be animated. */
+    void moveToPosition(PointF position, boolean animateMovement) {
+        if (Flags.floatingMenuImeDisplacementAnimation()) {
+            moveToPositionX(position.x, animateMovement);
+            moveToPositionY(position.y, animateMovement);
+        } else {
+            moveToPositionX(position.x, /* animateMovement = */ false);
+            moveToPositionY(position.y, /* animateMovement = */ false);
+        }
     }
 
     void moveToPositionX(float positionX) {
-        DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX);
+        moveToPositionX(positionX, /* animateMovement = */ false);
     }
 
-    private void moveToPositionY(float positionY) {
-        DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY);
+    void moveToPositionX(float positionX, boolean animateMovement) {
+        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+            springMenuWith(DynamicAnimation.TRANSLATION_X,
+                    createSpringForce(),
+                    /* velocity = */ 0,
+                    positionX, /* writeToPosition = */ false);
+        } else {
+            DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX);
+        }
+    }
+
+    void moveToPositionY(float positionY) {
+        moveToPositionY(positionY, /* animateMovement = */ false);
+    }
+
+    void moveToPositionY(float positionY, boolean animateMovement) {
+        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+            springMenuWith(DynamicAnimation.TRANSLATION_Y,
+                    createSpringForce(),
+                    /* velocity = */ 0,
+                    positionY, /* writeToPosition = */ false);
+        } else {
+            DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY);
+        }
     }
 
     void moveToPositionYIfNeeded(float positionY) {
@@ -151,7 +184,7 @@
     void moveAndPersistPosition(PointF position) {
         moveToPosition(position);
         mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
-        constrainPositionAndUpdate(position);
+        constrainPositionAndUpdate(position, /* writeToPosition = */ true);
     }
 
     void removeMenu() {
@@ -180,17 +213,13 @@
         flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
                 startXVelocity,
                 FLING_FRICTION_SCALAR,
-                new SpringForce()
-                        .setStiffness(SPRING_STIFFNESS)
-                        .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
+                createSpringForce(),
                 finalPositionX);
 
         flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
                 velocityY,
                 FLING_FRICTION_SCALAR,
-                new SpringForce()
-                        .setStiffness(SPRING_STIFFNESS)
-                        .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
+                createSpringForce(),
                 /* finalPosition= */ null);
     }
 
@@ -226,7 +255,8 @@
                     final float endPosition = finalPosition != null
                             ? finalPosition
                             : Math.max(min, Math.min(max, endValue));
-                    springMenuWith(property, spring, endVelocity, endPosition);
+                    springMenuWith(property, spring, endVelocity, endPosition,
+                            /* writeToPosition = */ true);
                 });
 
         cancelAnimation(property);
@@ -242,7 +272,7 @@
 
     @VisibleForTesting
     void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
-            float velocity, float finalPosition) {
+            float velocity, float finalPosition, boolean writeToPosition) {
         final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property);
         final SpringAnimation springAnimation =
                 new SpringAnimation(mMenuView, menuPositionProperty)
@@ -257,7 +287,7 @@
                                             DynamicAnimation::isRunning);
                             if (!areAnimationsRunning) {
                                 onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(),
-                                        mMenuView.getTranslationY()));
+                                        mMenuView.getTranslationY()), writeToPosition);
                             }
                         })
                         .setStartVelocity(velocity);
@@ -281,7 +311,8 @@
         if (currentXTranslation < draggableBounds.left
                 || currentXTranslation > draggableBounds.right) {
             constrainPositionAndUpdate(
-                    new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()));
+                    new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()),
+                    /* writeToPosition = */ true);
             moveToEdgeAndHide();
             return true;
         }
@@ -298,15 +329,19 @@
         return mMenuView.isMoveToTucked();
     }
 
-    void moveToEdgeAndHide() {
-        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
-
+    PointF getTuckedMenuPosition() {
         final PointF position = mMenuView.getMenuPosition();
         final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f;
         final float endX = isOnLeftSide()
                 ? position.x - menuHalfWidth
                 : position.x + menuHalfWidth;
-        moveToPosition(new PointF(endX, position.y));
+        return new PointF(endX, position.y);
+    }
+
+    void moveToEdgeAndHide() {
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
+        final PointF position = mMenuView.getMenuPosition();
+        moveToPosition(getTuckedMenuPosition());
 
         // Keep the touch region let users could click extra space to pop up the menu view
         // from the screen edge
@@ -335,6 +370,11 @@
         mPositionAnimations.get(property).cancel();
     }
 
+    @VisibleForTesting
+    DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) {
+        return mPositionAnimations.getOrDefault(property, null);
+    }
+
     void onDraggingStart() {
         mMenuView.onDraggingStart();
     }
@@ -361,9 +401,9 @@
                 .start();
     }
 
-    private void onSpringAnimationsEnd(PointF position) {
+    private void onSpringAnimationsEnd(PointF position, boolean writeToPosition) {
         mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
-        constrainPositionAndUpdate(position);
+        constrainPositionAndUpdate(position, writeToPosition);
 
         fadeOutIfEnabled();
 
@@ -372,7 +412,7 @@
         }
     }
 
-    private void constrainPositionAndUpdate(PointF position) {
+    private void constrainPositionAndUpdate(PointF position, boolean writeToPosition) {
         final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme();
         // Have the space gap margin between the top bound and the menu view, so actually the
         // position y range needs to cut the margin.
@@ -384,7 +424,12 @@
         final float percentageY = position.y < 0 || draggableBounds.height() == 0
                 ? MIN_PERCENT
                 : Math.min(MAX_PERCENT, position.y / draggableBounds.height());
-        mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
+
+        if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+            mMenuView.onEdgeChangedIfNeeded();
+        } else {
+            mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
+        }
     }
 
     void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
@@ -463,4 +508,11 @@
             mProperty.setValue(menuView, value);
         }
     }
+
+    @VisibleForTesting
+    static SpringForce createSpringForce() {
+        return new SpringForce()
+                .setStiffness(SPRING_STIFFNESS)
+                .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO);
+    }
 }
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 e1612b0..ea5a56c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -54,7 +54,6 @@
     private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>();
     private final AccessibilityTargetAdapter mAdapter;
     private final MenuViewModel mMenuViewModel;
-    private final MenuAnimationController mMenuAnimationController;
     private final Rect mBoundsInParent = new Rect();
     private final RecyclerView mTargetFeaturesView;
     private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater =
@@ -70,6 +69,7 @@
 
     private boolean mIsMoveToTucked;
 
+    private final MenuAnimationController mMenuAnimationController;
     private OnTargetFeaturesChangeListener mFeaturesChangeListener;
 
     MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
@@ -197,8 +197,30 @@
     }
 
     void onPositionChanged() {
-        final PointF position = mMenuViewAppearance.getMenuPosition();
-        mMenuAnimationController.moveToPosition(position);
+        onPositionChanged(/* animateMovement = */ false);
+    }
+
+    void onPositionChanged(boolean animateMovement) {
+        final PointF position;
+        if (isMoveToTucked()) {
+            position = mMenuAnimationController.getTuckedMenuPosition();
+        } else {
+            position = getMenuPosition();
+        }
+
+        // We can skip animating if FAB is not visible
+        if (Flags.floatingMenuImeDisplacementAnimation()
+                && animateMovement && getVisibility() == VISIBLE) {
+            mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
+            // onArrivalAtPosition() is called at the end of the animation.
+        } else {
+            mMenuAnimationController.moveToPosition(position);
+            onArrivalAtPosition(); // no animation, so we call this immediately.
+        }
+    }
+
+    void onArrivalAtPosition() {
+        final PointF position = getMenuPosition();
         onBoundsInParentChanged((int) position.x, (int) position.y);
 
         if (isMoveToTucked()) {
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 b6e8997..fbca022 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -59,6 +59,7 @@
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.bubbles.DismissViewUtils;
@@ -331,7 +332,7 @@
             mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop);
 
             mMenuView.onEdgeChanged();
-            mMenuView.onPositionChanged();
+            mMenuView.onPositionChanged(/* animateMovement = */ true);
 
             mImeInsetsRect.set(imeInsetsRect);
         }
@@ -362,6 +363,10 @@
 
             mMenuAnimationController.startTuckedAnimationPreview();
         }
+
+        if (Flags.floatingMenuImeDisplacementAnimation()) {
+            mMenuView.onArrivalAtPosition();
+        }
     }
 
     private CharSequence getMigrationMessage() {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 0e339dd..9305ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -28,17 +28,17 @@
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
 import com.android.systemui.assist.ui.DefaultUiController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.settings.SecureSettings;
 
 import dagger.Lazy;
@@ -144,6 +144,7 @@
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
     private final SecureSettings mSecureSettings;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private final DeviceProvisionedController mDeviceProvisionedController;
 
@@ -152,16 +153,16 @@
 
     private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
             new IVisualQueryDetectionAttentionListener.Stub() {
-        @Override
-        public void onAttentionGained() {
-            handleVisualAttentionChanged(true);
-        }
+                @Override
+                public void onAttentionGained() {
+                    handleVisualAttentionChanged(true);
+                }
 
-        @Override
-        public void onAttentionLost() {
-            handleVisualAttentionChanged(false);
-        }
-    };
+                @Override
+                public void onAttentionLost() {
+                    handleVisualAttentionChanged(false);
+                }
+            };
 
     private final CommandQueue mCommandQueue;
     protected final AssistUtils mAssistUtils;
@@ -183,7 +184,8 @@
             @Main Handler uiHandler,
             UserTracker userTracker,
             DisplayTracker displayTracker,
-            SecureSettings secureSettings) {
+            SecureSettings secureSettings,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mCommandQueue = commandQueue;
@@ -195,6 +197,7 @@
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mSecureSettings = secureSettings;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         registerVoiceInteractionSessionListener();
         registerVisualQueryRecognitionStatusListener();
@@ -316,12 +319,13 @@
     public boolean shouldOverrideAssist(int invocationType) {
         return mAssistOverrideInvocationTypes != null
                 && Arrays.stream(mAssistOverrideInvocationTypes).anyMatch(
-                        override -> override == invocationType);
+                    override -> override == invocationType);
     }
 
     /**
      * @param invocationTypes The invocation types that will henceforth be handled via
-     *         OverviewProxy (Launcher); other invocation types should be handled by this class.
+     *                        OverviewProxy (Launcher); other invocation types should be handled by
+     *                        this class.
      */
     public void setAssistantOverridesRequested(int[] invocationTypes) {
         mAssistOverrideInvocationTypes = invocationTypes;
@@ -478,7 +482,7 @@
 
     @Nullable
     private ComponentName getAssistInfo() {
-        return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
+        return getAssistInfoForUser(mSelectedUserInteractor.getSelectedUserId());
     }
 
     public void showDisclosure() {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 1ee06cc..56273eb 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -35,18 +35,19 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.assist.AssistLogger;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.assist.AssistantSessionEvent;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.res.R;
+
+import dagger.Lazy;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
@@ -80,7 +81,8 @@
     @Inject
     public DefaultUiController(Context context, AssistLogger assistLogger,
             WindowManager windowManager, MetricsLogger metricsLogger,
-            Lazy<AssistManager> assistManagerLazy) {
+            Lazy<AssistManager> assistManagerLazy,
+            NavigationBarController navigationBarController) {
         mAssistLogger = assistLogger;
         mRoot = new FrameLayout(context);
         mWindowManager = windowManager;
@@ -103,6 +105,7 @@
 
         mInvocationLightsView = (InvocationLightsView)
                 LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false);
+        mInvocationLightsView.setNavigationBarController(navigationBarController);
         mRoot.addView(mInvocationLightsView);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index 4d89231..0cdb376 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -31,11 +31,10 @@
 import android.view.View;
 
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
-import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBar;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarTransitions;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 
@@ -64,6 +63,8 @@
     private final int mLightColor;
     @ColorInt
     private final int mDarkColor;
+    @Nullable
+    private NavigationBarController mNavigationBarController;
 
     // Allocate variable for screen location lookup to avoid memory alloc onDraw()
     private int[] mScreenLocation = new int[2];
@@ -279,12 +280,11 @@
 
     private void attemptRegisterNavBarListener() {
         if (!mRegistered) {
-            NavigationBarController controller = Dependency.get(NavigationBarController.class);
-            if (controller == null) {
+            if (mNavigationBarController == null) {
                 return;
             }
 
-            NavigationBar navBar = controller.getDefaultNavigationBar();
+            NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
@@ -296,12 +296,11 @@
 
     private void attemptUnregisterNavBarListener() {
         if (mRegistered) {
-            NavigationBarController controller = Dependency.get(NavigationBarController.class);
-            if (controller == null) {
+            if (mNavigationBarController == null) {
                 return;
             }
 
-            NavigationBar navBar = controller.getDefaultNavigationBar();
+            NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
@@ -310,4 +309,8 @@
             mRegistered = false;
         }
     }
+
+    public void setNavigationBarController(NavigationBarController navigationBarController) {
+        mNavigationBarController = navigationBarController;
+    }
 }
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 453a7a6..8ea867b 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
@@ -28,6 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.TraceUtils.Companion.async
+import com.android.systemui.util.TraceUtils.Companion.withContext
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlin.math.max
@@ -263,7 +265,7 @@
      *   not being throttled.
      */
     private suspend fun refreshThrottling(): Long {
-        return withContext(backgroundDispatcher) {
+        return withContext("$TAG#refreshThrottling", backgroundDispatcher) {
             val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
             val deadline = async { repository.getThrottlingEndTimestamp() }
             val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
@@ -311,6 +313,10 @@
                 DomainLayerAuthenticationMethodModel.Pattern
         }
     }
+
+    companion object {
+        const val TAG = "AuthenticationInteractor"
+    }
 }
 
 /** Result of a user authentication attempt. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt
deleted file mode 100644
index ca4b8ef..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt
+++ /dev/null
@@ -1,54 +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.biometrics
-
-/**
- * Interface for controlling the on finger down & on finger up events.
- */
-interface AlternateUdfpsTouchProvider {
-
-    /**
-     * onPointerDown:
-     *
-     * This operation is used to notify the Fingerprint HAL that
-     * a fingerprint has been detected on the device's screen.
-     *
-     * See fingerprint/ISession#onPointerDown for more details.
-     */
-    fun onPointerDown(pointerId: Long, x: Int, y: Int, minor: Float, major: Float)
-
-    /**
-     * onPointerUp:
-     *
-     * This operation can be invoked when the HAL is performing any one of: ISession#authenticate,
-     * ISession#enroll, ISession#detectInteraction. This operation is used to indicate
-     * that a fingerprint that was previously down, is now up.
-     *
-     * See fingerprint/ISession#onPointerUp for more details.
-     */
-    fun onPointerUp(pointerId: Long)
-
-    /**
-     * onUiReady:
-     *
-     * This operation is used by the callee to notify the Fingerprint HAL that SystemUI is
-     * correctly configured for the fingerprint capture.
-     *
-     * See fingerprint/ISession#onUiReady for more details.
-     */
-    fun onUiReady()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ /dev/null
@@ -1,118 +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.biometrics
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthBiometricFaceIconController"
-
-/** Face only icon animator for BiometricPrompt. */
-class AuthBiometricFaceIconController(
-        context: Context,
-        iconView: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
-    // false = dark to light, true = light to dark
-    private var lastPulseLightToDark = false
-
-    private var state: BiometricState = BiometricState.STATE_IDLE
-
-    init {
-        val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
-        iconView.layoutParams.width = size
-        iconView.layoutParams.height = size
-        showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
-    }
-
-    private fun startPulsing() {
-        lastPulseLightToDark = false
-        animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
-    }
-
-    private fun pulseInNextDirection() {
-        val iconRes = if (lastPulseLightToDark) {
-            R.drawable.face_dialog_pulse_dark_to_light
-        } else {
-            R.drawable.face_dialog_pulse_light_to_dark
-        }
-        animateIcon(iconRes, true /* repeat */)
-        lastPulseLightToDark = !lastPulseLightToDark
-    }
-
-    override fun handleAnimationEnd(drawable: Drawable) {
-        if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
-            pulseInNextDirection()
-        }
-    }
-
-    override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
-        val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
-        if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
-            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating
-            )
-        } else if (newState == BiometricState.STATE_AUTHENTICATING) {
-            startPulsing()
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating
-            )
-        } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_confirmed
-            )
-        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
-            animateIconOnce(R.drawable.face_dialog_error_to_idle)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_idle
-            )
-        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_error)
-            iconView.contentDescription = context.getString(
-                    R.string.keyguard_face_failed
-            )
-        } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
-            animateIconOnce(R.drawable.face_dialog_wink_from_dark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_IDLE) {
-            showStaticDrawable(R.drawable.face_dialog_idle_static)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_idle
-            )
-        } else {
-            Log.w(TAG, "Unhandled state: $newState")
-        }
-        state = newState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
deleted file mode 100644
index 09eabf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.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.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintAndFaceIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
-
-    override val actsAsConfirmButton: Boolean = true
-
-    override fun shouldAnimateIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ): Boolean = when (newState) {
-        STATE_PENDING_CONFIRMATION -> true
-        else -> super.shouldAnimateIconViewForTransition(oldState, newState)
-    }
-
-    @RawRes
-    override fun getAnimationForTransition(
-        oldState: BiometricState,
-        newState: BiometricState
-    ): Int? = when (newState) {
-        STATE_AUTHENTICATED -> {
-           if (oldState == STATE_PENDING_CONFIRMATION) {
-               R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
-           } else {
-               super.getAnimationForTransition(oldState, newState)
-           }
-        }
-        STATE_PENDING_CONFIRMATION -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                R.raw.fingerprint_dialogue_error_to_unlock_lottie
-            } else {
-                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
-            }
-        }
-        else -> super.getAnimationForTransition(oldState, newState)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
deleted file mode 100644
index 0ad3848..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ /dev/null
@@ -1,342 +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.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import android.content.Context.FINGERPRINT_SERVICE
-import android.hardware.fingerprint.FingerprintManager
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.airbnb.lottie.LottieAnimationView
-import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-
-/** Fingerprint only icon animator for BiometricPrompt.  */
-open class AuthBiometricFingerprintIconController(
-        context: Context,
-        iconView: LottieAnimationView,
-        protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
-    private val isSideFps: Boolean
-    private val isReverseDefaultRotation =
-            context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
-    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
-        set(value) {
-            if (field == value) {
-                return
-            }
-            iconViewOverlay.layoutParams.width = value.first
-            iconViewOverlay.layoutParams.height = value.second
-            iconView.layoutParams.width = value.first
-            iconView.layoutParams.height = value.second
-            field = value
-        }
-
-    init {
-        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
-                R.dimen.biometric_dialog_fingerprint_icon_width),
-                context.resources.getDimensionPixelSize(
-                        R.dimen.biometric_dialog_fingerprint_icon_height))
-        isSideFps =
-            (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
-                fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
-            } ?: false
-        preloadAssets(context)
-        val displayInfo = DisplayInfo()
-        context.display?.getDisplayInfo(displayInfo)
-        if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
-            iconView.rotation = 180f
-        }
-    }
-
-    private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
-        val displayInfo = DisplayInfo()
-        context.display?.getDisplayInfo(displayInfo)
-        val rotation = getRotationFromDefault(displayInfo.rotation)
-        val iconViewOverlayAnimation =
-                getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
-
-        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconViewOverlay.setAnimation(iconViewOverlayAnimation)
-        }
-
-        val iconContentDescription = getIconContentDescription(newState)
-        if (iconContentDescription != null) {
-            iconView.contentDescription = iconContentDescription
-        }
-
-        iconView.frame = 0
-        iconViewOverlay.frame = 0
-        if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
-            iconView.playAnimation()
-        }
-
-        if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
-            iconViewOverlay.playAnimation()
-        }
-
-        LottieColorUtils.applyDynamicColors(context, iconView)
-        LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
-    }
-
-    private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
-        val icon = getAnimationForTransition(lastState, newState) ?: return
-
-        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconView.setAnimation(icon)
-        }
-
-        val iconContentDescription = getIconContentDescription(newState)
-        if (iconContentDescription != null) {
-            iconView.contentDescription = iconContentDescription
-        }
-
-        iconView.frame = 0
-        if (shouldAnimateIconViewForTransition(lastState, newState)) {
-            iconView.playAnimation()
-        }
-        LottieColorUtils.applyDynamicColors(context, iconView)
-    }
-
-    override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
-        if (isSideFps) {
-            updateIconSideFps(lastState, newState)
-        } else {
-            iconViewOverlay.visibility = View.GONE
-            updateIconNormal(lastState, newState)
-        }
-    }
-
-    @VisibleForTesting
-    fun getIconContentDescription(newState: BiometricState): CharSequence? {
-        val id = when (newState) {
-            STATE_IDLE,
-            STATE_AUTHENTICATING_ANIMATING_IN,
-            STATE_AUTHENTICATING,
-            STATE_AUTHENTICATED ->
-                if (isSideFps) {
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                } else {
-                    R.string.fingerprint_dialog_touch_sensor
-                }
-            STATE_PENDING_CONFIRMATION ->
-                if (isSideFps) {
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                } else {
-                    R.string.fingerprint_dialog_authenticated_confirmation
-                }
-            STATE_ERROR,
-            STATE_HELP -> R.string.biometric_dialog_try_again
-            else -> null
-        }
-        return if (id != null) context.getString(id) else null
-    }
-
-    protected open fun shouldAnimateIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    private fun shouldAnimateSfpsIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING ->
-            oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    protected open fun shouldAnimateIconViewOverlayForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    @RawRes
-    protected open fun getAnimationForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ): Int? {
-        val id = when (newState) {
-            STATE_HELP,
-            STATE_ERROR -> {
-                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
-            }
-            STATE_AUTHENTICATING_ANIMATING_IN,
-            STATE_AUTHENTICATING -> {
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
-                } else {
-                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
-                }
-            }
-            STATE_AUTHENTICATED -> {
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.raw.fingerprint_dialogue_error_to_success_lottie
-                } else {
-                    R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
-                }
-            }
-            else -> return null
-        }
-        return if (id != null) return id else null
-    }
-
-    private fun getRotationFromDefault(rotation: Int): Int =
-            if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-
-    @RawRes
-    private fun getSideFpsOverlayAnimationForTransition(
-            oldState: BiometricState,
-            newState: BiometricState,
-            rotation: Int
-    ): Int? = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> {
-            when (rotation) {
-                Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                Surface.ROTATION_90 ->
-                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
-                Surface.ROTATION_180 ->
-                    R.raw.biometricprompt_fingerprint_to_error_landscape
-                Surface.ROTATION_270 ->
-                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
-                else -> R.raw.biometricprompt_fingerprint_to_error_landscape
-            }
-        }
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                }
-            } else {
-                when (rotation) {
-                    Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_fingerprint_to_error_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
-                    else -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                }
-            }
-        }
-        STATE_AUTHENTICATED -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_error_to_success_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_error_to_success_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_error_to_success_landscape
-                }
-            } else {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                }
-            }
-        }
-        else -> null
-    }
-
-    private fun preloadAssets(context: Context) {
-        if (isSideFps) {
-            cacheLottieAssetsInContext(
-                context,
-                R.raw.biometricprompt_fingerprint_to_error_landscape,
-                R.raw.biometricprompt_folded_base_bottomright,
-                R.raw.biometricprompt_folded_base_default,
-                R.raw.biometricprompt_folded_base_topleft,
-                R.raw.biometricprompt_landscape_base,
-                R.raw.biometricprompt_portrait_base_bottomright,
-                R.raw.biometricprompt_portrait_base_topleft,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
-                R.raw.biometricprompt_symbol_error_to_success_landscape,
-                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
-                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
-                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
-                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
-            )
-        } else {
-            cacheLottieAssetsInContext(
-                context,
-                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
-                R.raw.fingerprint_dialogue_error_to_success_lottie,
-                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
-                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 054bd08..8d1d905 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.biometrics
 
 import android.annotation.MainThread
@@ -25,7 +41,7 @@
             shadeExpansionCollectorJob =
                 scope.launch {
                     // wait for it to emit true once
-                    shadeInteractorLazy.get().isAnyExpanding.first { it }
+                    shadeInteractorLazy.get().isUserInteracting.first { it }
                     onShadeInteraction.run()
                 }
             shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
deleted file mode 100644
index 958213a..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ /dev/null
@@ -1,100 +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.biometrics
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.drawable.Animatable2
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthIconController"
-
-/** Controller for animating the BiometricPrompt icon/affordance. */
-abstract class AuthIconController(
-    protected val context: Context,
-    protected val iconView: LottieAnimationView
-) : Animatable2.AnimationCallback() {
-
-    /** If this controller should ignore events and pause. */
-    var deactivated: Boolean = false
-
-    /** If the icon view should be treated as an alternate "confirm" button. */
-    open val actsAsConfirmButton: Boolean = false
-
-    final override fun onAnimationStart(drawable: Drawable) {
-        super.onAnimationStart(drawable)
-    }
-
-    final override fun onAnimationEnd(drawable: Drawable) {
-        super.onAnimationEnd(drawable)
-
-        if (!deactivated) {
-            handleAnimationEnd(drawable)
-        }
-    }
-
-    /** Set the icon to a static image. */
-    protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
-        iconView.setImageDrawable(context.getDrawable(iconRes))
-    }
-
-    /** Animate a resource. */
-    protected fun animateIconOnce(@DrawableRes iconRes: Int) {
-        animateIcon(iconRes, false)
-    }
-
-    /** Animate a resource. */
-    protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
-        if (!deactivated) {
-            val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
-            iconView.setImageDrawable(icon)
-            icon.forceAnimationOnUI()
-            if (repeat) {
-                icon.registerAnimationCallback(this)
-            }
-            icon.start()
-        }
-    }
-
-    /** Update the icon to reflect the [newState]. */
-    fun updateState(lastState: BiometricState, newState: BiometricState) {
-        if (deactivated) {
-            Log.w(TAG, "Ignoring updateState when deactivated: $newState")
-        } else {
-            updateIcon(lastState, newState)
-        }
-    }
-
-    /** Call during [updateState] if the controller is not [deactivated]. */
-    abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
-
-    /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
-    open fun handleAnimationEnd(drawable: Drawable) {}
-
-    // TODO(b/251476085): Migrate this to an extension at the appropriate level?
-    /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
-    protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
-        for (res in rawResources) {
-            LottieCompositionFactory.fromRawRes(context, res)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c1f6259..40f229b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -41,6 +41,8 @@
 import android.view.Surface
 import android.view.View
 import android.view.View.AccessibilityDelegate
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
 import android.view.ViewPropertyAnimator
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
@@ -54,13 +56,13 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.keyguard.KeyguardPINView
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
 import com.android.systemui.util.boundsOnScreen
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.traceSection
@@ -229,6 +231,20 @@
         }
     }
 
+    /** Hide the arrow indicator. */
+    fun hideIndicator() {
+        val lottieAnimationView =
+            overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+        lottieAnimationView?.visibility = INVISIBLE
+    }
+
+    /** Show the arrow indicator. */
+    fun showIndicator() {
+        val lottieAnimationView =
+            overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+        lottieAnimationView?.visibility = VISIBLE
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("requests:")
         for (requestSource in requests) {
@@ -247,6 +263,10 @@
         pw.println("     displayId=${displayInfo.uniqueId}")
         pw.println("     sensorType=${sensorProps?.sensorType}")
         pw.println("     location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+        pw.println("lottieAnimationView:")
+        pw.println(
+            "     visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}"
+        )
 
         pw.println("overlayOffsets=$overlayOffsets")
         pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
@@ -498,5 +518,5 @@
     AUTO_SHOW,
     /** Pin, pattern or password bouncer */
     PRIMARY_BOUNCER,
-    ALTERNATE_BOUNCER
+    ALTERNATE_BOUNCER,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index bdad413..395f68c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,9 +37,6 @@
     private float mDialogSuggestedAlpha = 1f;
     private float mNotificationShadeExpansion = 0f;
 
-    // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
-    boolean mUseExpandedOverlay = false;
-
     // mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
     private int mAlpha;
     boolean mPauseAuth;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 79d9c1b..92eacf1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -32,7 +32,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.SensorProperties;
@@ -54,7 +53,6 @@
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -84,7 +82,6 @@
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
@@ -100,9 +97,9 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
-import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.SystemClock;
 
 import kotlin.Unit;
@@ -110,7 +107,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -136,13 +132,8 @@
     private static final String TAG = "UdfpsController";
     private static final long AOD_SEND_FINGER_UP_DELAY_MILLIS = 1000;
 
-    // Minimum required delay between consecutive touch logs in milliseconds.
-    private static final long MIN_TOUCH_LOG_INTERVAL = 50;
     private static final long MIN_UNCHANGED_INTERACTION_LOG_INTERVAL = 50;
 
-    // This algorithm checks whether the touch is within the sensor's bounding box.
-    private static final int BOUNDING_BOX_TOUCH_CONFIG_ID = 0;
-
     private final Context mContext;
     private final Execution mExecution;
     private final FingerprintManager mFingerprintManager;
@@ -175,10 +166,9 @@
     @Nullable private final TouchProcessor mTouchProcessor;
     @NonNull private final SessionTracker mSessionTracker;
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @NonNull private final SecureSettings mSecureSettings;
-    @NonNull private final UdfpsUtils mUdfpsUtils;
     @NonNull private final InputManager mInputManager;
     @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    @NonNull private final SelectedUserInteractor mSelectedUserInteractor;
     private final boolean mIgnoreRefreshRate;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -187,11 +177,8 @@
     @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
-    @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider;
     @Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode;
 
-    // Tracks the velocity of a touch to help filter out the touches that move too fast.
-    @Nullable private VelocityTracker mVelocityTracker;
     // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
     private int mActivePointerId = -1;
     // Whether a pointer has been pilfered for current gesture
@@ -259,8 +246,6 @@
         final int touchConfigId = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_selected_udfps_touch_detection);
         pw.println("mSensorProps=(" + mSensorProps + ")");
-        pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled(
-                Flags.UDFPS_NEW_TOUCH_DETECTION));
         pw.println("touchConfigId: " + touchConfigId);
     }
 
@@ -272,7 +257,6 @@
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(
                         mContext,
-                        mFingerprintManager,
                         mInflater,
                         mWindowManager,
                         mAccessibilityManager,
@@ -286,7 +270,6 @@
                         mKeyguardStateController,
                         mUnlockedScreenOffAnimationController,
                         mUdfpsDisplayMode,
-                        mSecureSettings,
                         requestId,
                         reason,
                         callback,
@@ -299,9 +282,9 @@
                         mFeatureFlags,
                         mPrimaryBouncerInteractor,
                         mAlternateBouncerInteractor,
-                        mUdfpsUtils,
                         mUdfpsKeyguardAccessibilityDelegate,
-                        mUdfpsKeyguardViewModels
+                        mUdfpsKeyguardViewModels,
+                            mSelectedUserInteractor
                     )));
         }
 
@@ -376,13 +359,9 @@
          * Debug to run onUiReady
          */
         public void debugOnUiReady(int sensorId) {
-            if (UdfpsController.this.mAlternateTouchProvider != null) {
-                UdfpsController.this.mAlternateTouchProvider.onUiReady();
-            } else {
-                final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
-                UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
-                        FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
-            }
+            final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
+            UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
+                    FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
         }
     }
 
@@ -423,23 +402,6 @@
         mUdfpsDisplayMode = udfpsDisplayMode;
     }
 
-    /**
-     * Calculate the pointer speed given a velocity tracker and the pointer id.
-     * This assumes that the velocity tracker has already been passed all relevant motion events.
-     */
-    public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
-        final float vx = tracker.getXVelocity(pointerId);
-        final float vy = tracker.getYVelocity(pointerId);
-        return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
-    }
-
-    /**
-     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
-     */
-    public static boolean exceedsVelocityThreshold(float velocity) {
-        return velocity > 750f;
-    }
-
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -457,38 +419,6 @@
         }
     };
 
-    /**
-     * Forwards touches to the udfps controller / view
-     */
-    public boolean onTouch(MotionEvent event) {
-        if (mOverlay == null || mOverlay.isHiding()) {
-            return false;
-        }
-        // TODO(b/225068271): may not be correct but no way to get the id yet
-        return onTouch(mOverlay.getRequestId(), event, false);
-    }
-
-    /**
-     * @param x                   coordinate
-     * @param y                   coordinate
-     * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else,
-     *                            calculate from the display dimensions in portrait orientation
-     */
-    private boolean isWithinSensorArea(UdfpsView udfpsView, float x, float y,
-            boolean relativeToUdfpsView) {
-        if (relativeToUdfpsView) {
-            // TODO: move isWithinSensorArea to UdfpsController.
-            return udfpsView.isWithinSensorArea(x, y);
-        }
-
-        if (mOverlay == null || mOverlay.getAnimationViewController() == null) {
-            return false;
-        }
-
-        return !mOverlay.getAnimationViewController().shouldPauseAuth()
-                && mOverlayParams.getSensorBounds().contains((int) x, (int) y);
-    }
-
     private void tryDismissingKeyguard() {
         if (!mOnFingerDown) {
             playStartHaptic();
@@ -497,15 +427,6 @@
         mAttemptedToDismissKeyguard = true;
     }
 
-    @VisibleForTesting
-    boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
-        if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-            return newOnTouch(requestId, event, fromUdfpsView);
-        } else {
-            return oldOnTouch(requestId, event, fromUdfpsView);
-        }
-    }
-
     private int getBiometricSessionType() {
         if (mOverlay == null) {
             return -1;
@@ -566,7 +487,7 @@
         }
     }
 
-    private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
+    private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
         if (!fromUdfpsView) {
             Log.e(TAG, "ignoring the touch injected from outside of UdfpsView");
             return false;
@@ -580,7 +501,6 @@
                     + mOverlay.getRequestId());
             return false;
         }
-
         if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
                 && !mAlternateBouncerInteractor.isVisibleState())
                 || mPrimaryBouncerInteractor.isInTransit()) {
@@ -654,8 +574,7 @@
                 break;
 
             case UNCHANGED:
-                if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(),
-                        true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID
+                if (mActivePointerId == MotionEvent.INVALID_POINTER_ID
                         && mAlternateBouncerInteractor.isVisibleState()) {
                     // No pointer on sensor, forward to keyguard if alternateBouncer is visible
                     mKeyguardViewManager.onTouch(event);
@@ -667,7 +586,7 @@
         logBiometricTouch(processedTouch.getEvent(), data);
 
         // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
-        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
+        if (mActivePointerId != MotionEvent.INVALID_POINTER_ID
                 || mAlternateBouncerInteractor.isVisibleState()) {
             shouldPilfer = true;
         }
@@ -680,146 +599,7 @@
             mPointerPilfered = true;
         }
 
-        return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
-    }
-
-    private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
-        if (mOverlay == null) {
-            Log.w(TAG, "ignoring onTouch with null overlay");
-            return false;
-        }
-        if (!mOverlay.matchesRequestId(requestId)) {
-            Log.w(TAG, "ignoring stale touch event: " + requestId + " current: "
-                    + mOverlay.getRequestId());
-            return false;
-        }
-
-        final UdfpsView udfpsView = mOverlay.getOverlayView();
-        boolean handled = false;
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_HOVER_ENTER:
-                Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN");
-                // To simplify the lifecycle of the velocity tracker, make sure it's never null
-                // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
-                if (mVelocityTracker == null) {
-                    mVelocityTracker = VelocityTracker.obtain();
-                } else {
-                    // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
-                    // ACTION_DOWN, in that case we should just reuse the old instance.
-                    mVelocityTracker.clear();
-                }
-
-                final boolean withinSensorArea =
-                        isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
-                if (withinSensorArea) {
-                    Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
-                    Log.v(TAG, "onTouch | action down");
-                    // The pointer that causes ACTION_DOWN is always at index 0.
-                    // We need to persist its ID to track it during ACTION_MOVE that could include
-                    // data for many other pointers because of multi-touch support.
-                    mActivePointerId = event.getPointerId(0);
-                    mVelocityTracker.addMovement(event);
-                    handled = true;
-                    mAcquiredReceived = false;
-                }
-                if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) {
-                    Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN");
-                    tryDismissingKeyguard();
-                }
-
-                Trace.endSection();
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-            case MotionEvent.ACTION_HOVER_MOVE:
-                Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE");
-                final int idx = mActivePointerId == -1
-                        ? event.getPointerId(0)
-                        : event.findPointerIndex(mActivePointerId);
-                if (idx == event.getActionIndex()) {
-                    final boolean actionMoveWithinSensorArea =
-                            isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
-                                    fromUdfpsView);
-                    if ((fromUdfpsView || actionMoveWithinSensorArea)
-                            && shouldTryToDismissKeyguard()) {
-                        Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
-                        tryDismissingKeyguard();
-                        break;
-                    }
-                    // Map the touch to portrait mode if the device is in landscape mode.
-                    final Point scaledTouch = mUdfpsUtils.getTouchInNativeCoordinates(
-                            idx, event, mOverlayParams);
-                    if (actionMoveWithinSensorArea) {
-                        if (mVelocityTracker == null) {
-                            // touches could be injected, so the velocity tracker may not have
-                            // been initialized (via ACTION_DOWN).
-                            mVelocityTracker = VelocityTracker.obtain();
-                        }
-                        mVelocityTracker.addMovement(event);
-                        // Compute pointer velocity in pixels per second.
-                        mVelocityTracker.computeCurrentVelocity(1000);
-                        // Compute pointer speed from X and Y velocities.
-                        final float v = computePointerSpeed(mVelocityTracker, mActivePointerId);
-                        final float minor = event.getTouchMinor(idx);
-                        final float major = event.getTouchMajor(idx);
-                        final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v);
-                        final String touchInfo = String.format(
-                                "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
-                                minor, major, v, exceedsVelocityThreshold);
-                        final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
-
-                        if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) {
-                            final float scale = mOverlayParams.getScaleFactor();
-                            float scaledMinor = minor / scale;
-                            float scaledMajor = major / scale;
-                            onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor,
-                                    scaledMajor);
-
-                            Log.v(TAG, "onTouch | finger down: " + touchInfo);
-                            mTouchLogTime = mSystemClock.elapsedRealtime();
-                            handled = true;
-                        } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
-                            Log.v(TAG, "onTouch | finger move: " + touchInfo);
-                            mTouchLogTime = mSystemClock.elapsedRealtime();
-                        }
-                    } else {
-                        Log.v(TAG, "onTouch | finger outside");
-                        onFingerUp(requestId, udfpsView);
-                        // Maybe announce for accessibility.
-                        mFgExecutor.execute(() -> {
-                            if (mOverlay == null) {
-                                Log.e(TAG, "touch outside sensor area received"
-                                        + "but serverRequest is null");
-                                return;
-                            }
-                            mOverlay.onTouchOutsideOfSensorArea(scaledTouch);
-                        });
-                    }
-                }
-                Trace.endSection();
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_HOVER_EXIT:
-                Trace.beginSection("UdfpsController.onTouch.ACTION_UP");
-                mActivePointerId = -1;
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                }
-                Log.v(TAG, "onTouch | finger up");
-                mAttemptedToDismissKeyguard = false;
-                onFingerUp(requestId, udfpsView);
-                mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
-                Trace.endSection();
-                break;
-
-            default:
-                // Do nothing.
-        }
-        return handled;
+        return mActivePointerId != MotionEvent.INVALID_POINTER_ID;
     }
 
     private boolean shouldTryToDismissKeyguard() {
@@ -859,18 +639,16 @@
             @NonNull SystemUIDialogManager dialogManager,
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
-            @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider,
             @NonNull @BiometricsBackground Executor biometricsExecutor,
             @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
             @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
             @NonNull SessionTracker sessionTracker,
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
-            @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
-            @NonNull UdfpsUtils udfpsUtils,
             @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
-            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
+            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
+            @NonNull SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -900,7 +678,6 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
-        mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null);
         mSensorProps = new FingerprintSensorPropertiesInternal(
                 -1 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
@@ -912,13 +689,11 @@
         mBiometricExecutor = biometricsExecutor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mSecureSettings = secureSettings;
-        mUdfpsUtils = udfpsUtils;
         mInputManager = inputManager;
         mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
+        mSelectedUserInteractor = selectedUserInteractor;
 
-        mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-                ? singlePointerTouchProcessor : null;
+        mTouchProcessor = singlePointerTouchProcessor;
         mSessionTracker = sessionTracker;
 
         mDumpManager.registerDumpable(TAG, this);
@@ -1172,16 +947,9 @@
     }
 
     private void dispatchOnUiReady(long requestId) {
-        if (mAlternateTouchProvider != null) {
-            mBiometricExecutor.execute(() -> {
-                mAlternateTouchProvider.onUiReady();
-                mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
-            });
-        } else {
-            mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
-                    mSensorProps.sensorId);
-            mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
-        }
+        mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
+                mSensorProps.sensorId);
+        mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
     }
 
     private void onFingerDown(
@@ -1241,24 +1009,8 @@
             }
         }
         mOnFingerDown = true;
-        if (mAlternateTouchProvider != null) {
-            mBiometricExecutor.execute(() -> {
-                mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major);
-            });
-            mFgExecutor.execute(() -> {
-                if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                    mKeyguardUpdateMonitor.onUdfpsPointerDown((int) requestId);
-                }
-            });
-        } else {
-            if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
-                        minor, major, orientation, time, gestureStart, isAod);
-            } else {
-                mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x,
-                        (int) y, minor, major);
-            }
-        }
+        mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
+                minor, major, orientation, time, gestureStart, isAod);
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
         final UdfpsView view = mOverlay.getOverlayView();
         if (view != null && isOptical()) {
@@ -1305,23 +1057,8 @@
         mActivePointerId = -1;
         mAcquiredReceived = false;
         if (mOnFingerDown) {
-            if (mAlternateTouchProvider != null) {
-                mBiometricExecutor.execute(() -> {
-                    mAlternateTouchProvider.onPointerUp(requestId);
-                });
-                mFgExecutor.execute(() -> {
-                    if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                        mKeyguardUpdateMonitor.onUdfpsPointerUp((int) requestId);
-                    }
-                });
-            } else {
-                if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                    mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
-                            y, minor, major, orientation, time, gestureStart, isAod);
-                } else {
-                    mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
-                }
-            }
+            mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
+                    y, minor, major, orientation, time, gestureStart, isAod);
             for (Callback cb : mCallbacks) {
                 cb.onFingerUp();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 34a0d8a..934f9f9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -20,7 +20,6 @@
 import android.annotation.UiThread
 import android.content.Context
 import android.graphics.PixelFormat
-import android.graphics.Point
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
@@ -29,7 +28,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
-import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.os.Build
 import android.os.RemoteException
@@ -46,26 +44,24 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
-import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import javax.inject.Provider
 
@@ -82,34 +78,32 @@
 @ExperimentalCoroutinesApi
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
-        private val context: Context,
-        fingerprintManager: FingerprintManager,
-        private val inflater: LayoutInflater,
-        private val windowManager: WindowManager,
-        private val accessibilityManager: AccessibilityManager,
-        private val statusBarStateController: StatusBarStateController,
-        private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
-        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-        private val dialogManager: SystemUIDialogManager,
-        private val dumpManager: DumpManager,
-        private val transitionController: LockscreenShadeTransitionController,
-        private val configurationController: ConfigurationController,
-        private val keyguardStateController: KeyguardStateController,
-        private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
-        private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
-        private val secureSettings: SecureSettings,
-        val requestId: Long,
-        @ShowReason val requestReason: Int,
-        private val controllerCallback: IUdfpsOverlayControllerCallback,
-        private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
-        private val activityLaunchAnimator: ActivityLaunchAnimator,
-        private val featureFlags: FeatureFlags,
-        private val primaryBouncerInteractor: PrimaryBouncerInteractor,
-        private val alternateBouncerInteractor: AlternateBouncerInteractor,
-        private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
-        private val udfpsUtils: UdfpsUtils,
-        private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
-        private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+    private val context: Context,
+    private val inflater: LayoutInflater,
+    private val windowManager: WindowManager,
+    private val accessibilityManager: AccessibilityManager,
+    private val statusBarStateController: StatusBarStateController,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val dialogManager: SystemUIDialogManager,
+    private val dumpManager: DumpManager,
+    private val transitionController: LockscreenShadeTransitionController,
+    private val configurationController: ConfigurationController,
+    private val keyguardStateController: KeyguardStateController,
+    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+    private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+    val requestId: Long,
+    @ShowReason val requestReason: Int,
+    private val controllerCallback: IUdfpsOverlayControllerCallback,
+    private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val featureFlags: FeatureFlags,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
+    private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+    private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -134,10 +128,7 @@
         privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
         // Avoid announcing window title.
         accessibilityTitle = " "
-
-        if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-            inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
-        }
+        inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
     }
 
     /** If the overlay is currently showing. */
@@ -206,7 +197,6 @@
                         overlayTouchListener!!
                     )
                     overlayTouchListener?.onTouchExplorationStateChanged(true)
-                    useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 }
             } catch (e: RuntimeException) {
                 Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -249,15 +239,14 @@
             }
             REASON_AUTH_KEYGUARD -> {
                 if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
-                    udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
-                    UdfpsKeyguardViewController(
-                        view.addUdfpsView(R.layout.udfps_keyguard_view),
-                        statusBarStateController,
-                        primaryBouncerInteractor,
-                        dialogManager,
-                        dumpManager,
-                        alternateBouncerInteractor,
-                        udfpsKeyguardViewModels.get(),
+                    // note: empty controller, currently shows no visual affordance
+                    // instead SysUI will show the fingerprint icon in its DeviceEntryIconView
+                    UdfpsBpViewController(
+                            view.addUdfpsView(R.layout.udfps_bp_view),
+                            statusBarStateController,
+                            primaryBouncerInteractor,
+                            dialogManager,
+                            dumpManager
                     )
                 } else {
                     UdfpsKeyguardViewControllerLegacy(
@@ -279,6 +268,7 @@
                         primaryBouncerInteractor,
                         alternateBouncerInteractor,
                         udfpsKeyguardAccessibilityDelegate,
+                        selectedUserInteractor,
                     )
                 }
             }
@@ -331,25 +321,6 @@
         return wasShowing
     }
 
-    /**
-     * This function computes the angle of touch relative to the sensor and maps
-     * the angle to a list of help messages which are announced if accessibility is enabled.
-     *
-     */
-    fun onTouchOutsideOfSensorArea(scaledTouch: Point) {
-        val theStr =
-            udfpsUtils.onTouchOutsideOfSensorArea(
-                touchExplorationEnabled,
-                context,
-                scaledTouch.x,
-                scaledTouch.y,
-                overlayParams
-            )
-        if (theStr != null) {
-            animationViewController?.doAnnounceForAccessibility(theStr)
-        }
-    }
-
     /** Cancel this request. */
     fun cancel() {
         try {
@@ -367,10 +338,6 @@
     ): WindowManager.LayoutParams {
         val paddingX = animation?.paddingX ?: 0
         val paddingY = animation?.paddingY ?: 0
-        if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null &&
-                animation.listenForTouchesOutsideView()) {
-            flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-        }
 
         val isEnrollment = when (requestReason) {
             REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
@@ -379,19 +346,15 @@
 
         // Use expanded overlay unless touchExploration enabled
         var rotatedBounds =
-            if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
-                    Rect(overlayParams.sensorBounds)
-                } else {
-                    Rect(
-                        0,
-                        0,
-                        overlayParams.naturalDisplayWidth,
-                        overlayParams.naturalDisplayHeight
-                    )
-                }
-            } else {
+            if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
                 Rect(overlayParams.sensorBounds)
+            } else {
+                Rect(
+                    0,
+                    0,
+                    overlayParams.naturalDisplayWidth,
+                    overlayParams.naturalDisplayHeight
+                )
             }
 
         val rot = overlayParams.rotation
@@ -399,9 +362,9 @@
             if (!shouldRotate(animation)) {
                 Log.v(
                     TAG, "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) +
-                            " animation=$animation" +
-                            " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
-                            " isOccluded=${keyguardStateController.isOccluded}"
+                        " animation=$animation" +
+                        " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+                        " isOccluded=${keyguardStateController.isOccluded}"
                 )
             } else {
                 Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
@@ -412,14 +375,12 @@
                     rot
                 )
 
-                if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                    RotationUtils.rotateBounds(
-                            sensorBounds,
-                            overlayParams.naturalDisplayWidth,
-                            overlayParams.naturalDisplayHeight,
-                            rot
-                    )
-                }
+                RotationUtils.rotateBounds(
+                    sensorBounds,
+                    overlayParams.naturalDisplayWidth,
+                    overlayParams.naturalDisplayHeight,
+                    rot
+                )
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
index 8cc15da..afe37d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
@@ -43,10 +43,6 @@
         return fingerprintDrawablePlaceHolder
     }
 
-    fun useExpandedOverlay(useExpandedOverlay: Boolean) {
-        mUseExpandedOverlay = useExpandedOverlay
-    }
-
     fun isVisible(): Boolean {
         return visible
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 8ce98a9..d7df0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator
 import android.content.res.Configuration
 import android.util.MathUtils
-import android.view.MotionEvent
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
@@ -27,16 +26,15 @@
 import com.android.app.animation.Interpolators
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -47,6 +45,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.io.PrintWriter
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -70,6 +69,7 @@
     primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -80,8 +80,6 @@
     ),
     UdfpsKeyguardViewControllerAdapter {
     private val uniqueIdentifier = this.toString()
-    private val useExpandedOverlay: Boolean =
-        featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private var showingUdfpsBouncer = false
     private var udfpsRequested = false
     private var qsExpansion = 0f
@@ -192,24 +190,6 @@
                 updateAlpha()
                 updatePauseAuth()
             }
-
-            /**
-             * Forward touches to the UdfpsController. This allows the touch to start from outside
-             * the sensor area and then slide their finger into the sensor area.
-             */
-            override fun onTouch(event: MotionEvent) {
-                // Don't forward touches if the shade has already started expanding.
-                if (transitionToFullShadeProgress != 0f) {
-                    return
-                }
-
-                // Forwarding touches not needed with expanded overlay
-                if (useExpandedOverlay) {
-                    return
-                } else {
-                    udfpsController.onTouch(event)
-                }
-            }
         }
 
     private val occludingAppBiometricUI: OccludingAppBiometricUI =
@@ -294,7 +274,6 @@
         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
         lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
-        view.mUseExpandedOverlay = useExpandedOverlay
         view.startIconAsyncInflate {
             val animationViewInternal: View =
                 view.requireViewById(R.id.udfps_animation_view_internal)
@@ -407,7 +386,7 @@
         }
         if (
             keyguardUpdateMonitor.getUserUnlockedWithBiometric(
-                KeyguardUpdateMonitor.getCurrentUser()
+                selectedUserInteractor.getSelectedUserId()
             )
         ) {
             // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 36a42f9..95e3a76 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -298,45 +298,34 @@
         pw.println("    mUdfpsRequested=" + mUdfpsRequested);
         pw.println("    mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
         pw.println("    mAnimationType=" + mAnimationType);
-        pw.println("    mUseExpandedOverlay=" + mUseExpandedOverlay);
     }
 
     private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
             new AsyncLayoutInflater.OnInflateFinishedListener() {
-        @Override
-        public void onInflateFinished(View view, int resid, ViewGroup parent) {
-            mFullyInflated = true;
-            mAodFp = view.findViewById(R.id.udfps_aod_fp);
-            mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp);
-            mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg);
+                @Override
+                public void onInflateFinished(View view, int resid, ViewGroup parent) {
+                    mFullyInflated = true;
+                    mAodFp = view.findViewById(R.id.udfps_aod_fp);
+                    mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp);
+                    mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg);
 
-            updatePadding();
-            updateColor();
-            updateAlpha();
+                    updatePadding();
+                    updateColor();
+                    updateAlpha();
 
-            if (mUseExpandedOverlay) {
-                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                lp.width = mSensorBounds.width();
-                lp.height = mSensorBounds.height();
-                RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds));
-                lp.setMarginsRelative(
-                        (int) relativeToView.left,
-                        (int) relativeToView.top,
-                        (int) relativeToView.right,
-                        (int) relativeToView.bottom
-                );
-                parent.addView(view, lp);
-            } else {
-                parent.addView(view);
-            }
+                    final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                    lp.width = mSensorBounds.width();
+                    lp.height = mSensorBounds.height();
+                    RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds));
+                    lp.setMarginsRelative((int) relativeToView.left, (int) relativeToView.top,
+                            (int) relativeToView.right, (int) relativeToView.bottom);
+                    parent.addView(view, lp);
 
-            // requires call to invalidate to update the color
-            mLockScreenFp.addValueCallback(
-                    new KeyPath("**"), LottieProperty.COLOR_FILTER,
-                    frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
-                            PorterDuff.Mode.SRC_ATOP)
-            );
-            mOnFinishInflateRunnable.run();
-        }
-    };
+                    // requires call to invalidate to update the color
+                    mLockScreenFp.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER,
+                            frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
+                                    PorterDuff.Mode.SRC_ATOP));
+                    mOnFinishInflateRunnable.run();
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 6ce6172..76bcd6e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -19,14 +19,12 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.Paint
-import android.graphics.PointF
 import android.graphics.Rect
 import android.graphics.RectF
 import android.util.AttributeSet
 import android.util.Log
 import android.view.MotionEvent
 import android.widget.FrameLayout
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.doze.DozeReceiver
 
@@ -39,10 +37,6 @@
     context: Context,
     attrs: AttributeSet?
 ) : FrameLayout(context, attrs), DozeReceiver {
-
-    // Use expanded overlay when feature flag is true, set by UdfpsViewController
-    var useExpandedOverlay: Boolean = false
-
     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
     // overlayParams.sensorBounds
     var sensorRect = Rect()
@@ -53,14 +47,6 @@
         textSize = 32f
     }
 
-    private val sensorTouchAreaCoefficient: Float =
-        context.theme.obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 0).use { a ->
-            require(a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
-                "UdfpsView must contain sensorTouchAreaCoefficient"
-            }
-            a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f)
-        }
-
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
@@ -94,22 +80,8 @@
     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
         super.onLayout(changed, left, top, right, bottom)
 
-        val paddingX = animationViewController?.paddingX ?: 0
-        val paddingY = animationViewController?.paddingY ?: 0
-
         // Updates sensor rect in relation to the overlay view
-        if (useExpandedOverlay) {
-            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
-        } else {
-            sensorRect.set(
-                    paddingX,
-                    paddingY,
-                    (overlayParams.sensorBounds.width() + paddingX),
-                    (overlayParams.sensorBounds.height() + paddingY)
-            )
-
-            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
-        }
+        animationViewController?.onSensorRectUpdated(RectF(sensorRect))
     }
 
     override fun onAttachedToWindow() {
@@ -131,22 +103,6 @@
         }
     }
 
-    fun isWithinSensorArea(x: Float, y: Float): Boolean {
-        // The X and Y coordinates of the sensor's center.
-        val translation = animationViewController?.touchTranslation ?: PointF(0f, 0f)
-        val cx = sensorRect.centerX() + translation.x
-        val cy = sensorRect.centerY() + translation.y
-        // Radii along the X and Y axes.
-        val rx = (sensorRect.right - sensorRect.left) / 2.0f
-        val ry = (sensorRect.bottom - sensorRect.top) / 2.0f
-
-        return x > cx - rx * sensorTouchAreaCoefficient &&
-            x < cx + rx * sensorTouchAreaCoefficient &&
-            y > cy - ry * sensorTouchAreaCoefficient &&
-            y < cy + ry * sensorTouchAreaCoefficient &&
-            !(animationViewController?.shouldPauseAuth() ?: false)
-    }
-
     fun configureDisplay(onDisplayConfigured: Runnable) {
         isDisplayConfigured = true
         animationViewController?.onDisplayConfiguring()
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 c4c52e8b..050b399 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
@@ -42,8 +42,11 @@
 /** Repository for the current state of the display */
 interface DisplayStateRepository {
     /**
-     * Whether or not the direction rotation is applied to get to an application's requested
-     * orientation is reversed.
+     * If true, the direction rotation is applied to get to an application's requested orientation
+     * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+     * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+     * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+     * true here reverses that logic. See go/natural-orientation for context.
      */
     val isReverseDefaultRotation: Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
index a590dccd..b9b2fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
@@ -23,8 +23,6 @@
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
@@ -49,10 +47,4 @@
     @Binds
     @SysUISingleton
     fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
-
-    @Binds
-    @SysUISingleton
-    fun providesSideFpsOverlayInteractor(
-        impl: SideFpsOverlayInteractorImpl
-    ): SideFpsOverlayInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index f36a3ec..427361d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -54,6 +54,18 @@
     /** Current rotation of the display */
     val currentRotation: StateFlow<DisplayRotation>
 
+    /** Display change event indicating a change to the given displayId has occurred. */
+    val displayChanges: Flow<Int>
+
+    /**
+     * If true, the direction rotation is applied to get to an application's requested orientation
+     * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+     * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+     * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+     * true here reverses that logic. See go/natural-orientation for context.
+     */
+    val isReverseDefaultRotation: Boolean
+
     /** Called on configuration changes, used to keep the display state in sync */
     fun onConfigurationChanged(newConfig: Configuration)
 }
@@ -74,6 +86,8 @@
         screenSizeFoldProvider = foldProvider
     }
 
+    override val displayChanges = displayRepository.displayChangeEvent
+
     override val isFolded: Flow<Boolean> =
         conflatedCallbackFlow {
                 val sendFoldStateUpdate = { state: Boolean ->
@@ -107,6 +121,8 @@
     override val currentRotation: StateFlow<DisplayRotation> =
         displayStateRepository.currentRotation
 
+    override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         screenSizeFoldProvider.onConfigurationChange(newConfig)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
deleted file mode 100644
index 75ae061..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ /dev/null
@@ -1,62 +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.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
-
-/** Business logic for SideFps overlay offsets. */
-interface SideFpsOverlayInteractor {
-
-    /** The displayId of the current display. */
-    val displayId: Flow<String>
-
-    /** Overlay offsets corresponding to given displayId. */
-    val overlayOffsets: Flow<SensorLocationInternal>
-
-    /** Called on display changes, used to keep the display state in sync */
-    fun onDisplayChanged(displayId: String)
-}
-
-@SysUISingleton
-class SideFpsOverlayInteractorImpl
-@Inject
-constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
-    SideFpsOverlayInteractor {
-
-    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
-    override val displayId: Flow<String> = _displayId.asStateFlow()
-
-    override val overlayOffsets: Flow<SensorLocationInternal> =
-        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
-            offsets[displayId] ?: SensorLocationInternal.DEFAULT
-        }
-
-    override fun onDisplayChanged(displayId: String) {
-        _displayId.value = displayId
-    }
-
-    companion object {
-        private const val TAG = "SideFpsOverlayInteractorImpl"
-    }
-}
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
new file mode 100644
index 0000000..f85203e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import android.view.WindowManager
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+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.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class SideFpsSensorInteractor
+@Inject
+constructor(
+    private val context: Context,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
+    windowManager: WindowManager,
+    displayStateInteractor: DisplayStateInteractor,
+    featureFlags: FeatureFlagsClassic,
+) {
+
+    private val sensorForCurrentDisplay =
+        combine(
+                displayStateInteractor.displayChanges,
+                fingerprintPropertyRepository.sensorLocations,
+                ::Pair
+            )
+            .map { (_, locations) -> locations[context.display?.uniqueId] }
+            .filterNotNull()
+
+    val isAvailable: Flow<Boolean> =
+        fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
+
+    val authenticationDuration: Flow<Long> =
+        flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L)
+
+    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+        isAvailable.flatMapLatest { sfpsAvailable ->
+            if (sfpsAvailable) {
+                // todo (b/305236201) also add the settings check here.
+                flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK))
+            } else {
+                flowOf(false)
+            }
+        }
+
+    val sensorLocation: Flow<SideFpsSensorLocation> =
+        combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
+            (rotation, sensorLocation: SensorLocationInternal) ->
+            val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
+            // device dimensions in the current rotation
+            val size = windowManager.maximumWindowMetrics.bounds
+            val isDefaultOrientation = rotation.isDefaultOrientation()
+            // Width and height are flipped is device is not in rotation_0 or rotation_180
+            // Flipping it to the width and height of the device in default orientation.
+            val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+            val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+            val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0
+
+            val (sensorLeft, sensorTop) =
+                if (isSensorVerticalInDefaultOrientation) {
+                    when (rotation) {
+                        DisplayRotation.ROTATION_0 -> {
+                            Pair(displayWidth, sensorLocation.sensorLocationY)
+                        }
+                        DisplayRotation.ROTATION_90 -> {
+                            Pair(sensorLocation.sensorLocationY, 0)
+                        }
+                        DisplayRotation.ROTATION_180 -> {
+                            Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
+                        }
+                        DisplayRotation.ROTATION_270 -> {
+                            Pair(
+                                displayHeight - sensorLocation.sensorLocationY - sensorWidth,
+                                displayWidth
+                            )
+                        }
+                    }
+                } else {
+                    when (rotation) {
+                        DisplayRotation.ROTATION_0 -> {
+                            Pair(sensorLocation.sensorLocationX, 0)
+                        }
+                        DisplayRotation.ROTATION_90 -> {
+                            Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
+                        }
+                        DisplayRotation.ROTATION_180 -> {
+                            Pair(
+                                displayWidth - sensorLocation.sensorLocationX - sensorWidth,
+                                displayHeight
+                            )
+                        }
+                        DisplayRotation.ROTATION_270 -> {
+                            Pair(displayHeight, sensorLocation.sensorLocationX)
+                        }
+                    }
+                }
+
+            SideFpsSensorLocation(
+                left = sensorLeft,
+                top = sensorTop,
+                width = sensorWidth,
+                isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
+            )
+        }
+}
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 2a1047a..38043b4 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,13 +17,13 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.view.MotionEvent
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
@@ -35,11 +35,16 @@
 @SysUISingleton
 class UdfpsOverlayInteractor
 @Inject
-constructor(private val authController: AuthController, @Application scope: CoroutineScope) {
+constructor(
+    private val authController: AuthController,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    @Application scope: CoroutineScope
+) {
 
     /** Whether a touch is within the under-display fingerprint sensor area */
     fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
-        val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
+        val isUdfpsEnrolled =
+            authController.isUdfpsEnrolled(selectedUserInteractor.getSelectedUserId())
         val isWithinOverlayBounds =
             udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt())
         return isUdfpsEnrolled && isWithinOverlayBounds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
new file mode 100644
index 0000000..35f8e3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.model
+
+data class SideFpsSensorLocation(
+    /** Pixel offset from the left of the screen */
+    val left: Int,
+    /** Pixel offset from the top of the screen */
+    val top: Int,
+    /** Width in pixels of the SFPS sensor */
+    val width: Int,
+    /**
+     * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or
+     * Rotation_180)
+     */
+    val isSensorVerticalInDefaultOrientation: Boolean
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
index 10a3e91..336404c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
@@ -10,6 +10,9 @@
     ROTATION_270,
 }
 
+fun DisplayRotation.isDefaultOrientation() =
+    this == DisplayRotation.ROTATION_0 || this == DisplayRotation.ROTATION_180
+
 /** Converts [Surface.Rotation] to corresponding [DisplayRotation] */
 fun Int.toDisplayRotation(): DisplayRotation =
     when (this) {
@@ -19,3 +22,12 @@
         Surface.ROTATION_270 -> DisplayRotation.ROTATION_270
         else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this")
     }
+
+/** Converts [DisplayRotation] to corresponding [Surface.Rotation] */
+fun DisplayRotation.toRotation(): Int =
+    when (this) {
+        DisplayRotation.ROTATION_0 -> Surface.ROTATION_0
+        DisplayRotation.ROTATION_90 -> Surface.ROTATION_90
+        DisplayRotation.ROTATION_180 -> Surface.ROTATION_180
+        DisplayRotation.ROTATION_270 -> Surface.ROTATION_270
+    }
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 cef0be0..0d72b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -29,11 +29,10 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.systemui.res.R;
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthDialog;
 import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+import com.android.systemui.res.R;
 
 import kotlin.Pair;
 
@@ -85,13 +84,13 @@
     }
 
     @Deprecated
-    public void updateFingerprintAffordanceSize(
-            @NonNull AuthBiometricFingerprintIconController iconController) {
+    public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
         if (mUdfpsAdapter != null) {
             final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
                     mScaleFactorProvider.provide());
-            iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+            return new Pair(sensorDiameter, sensorDiameter);
         }
+        return null;
     }
 
     @NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index 56e3f39..2f493ac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -2,11 +2,11 @@
 
 import android.content.Context
 import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.graphics.Insets
 import android.text.TextUtils
 import android.util.AttributeSet
 import android.view.View
 import android.view.WindowInsets
-import android.view.WindowInsets.Type.ime
 import android.view.accessibility.AccessibilityManager
 import android.widget.LinearLayout
 import android.widget.TextView
@@ -41,7 +41,10 @@
     }
 
     override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
-        val imeBottomInset = insets.getInsets(ime()).bottom
+        val statusBarInsets: Insets = insets.getInsets(WindowInsets.Type.statusBars())
+        val keyboardInsets: Insets = insets.getInsets(WindowInsets.Type.ime())
+        val navigationInsets: Insets = insets.getInsets(WindowInsets.Type.navigationBars())
+        val imeBottomInset = keyboardInsets.bottom
         if (bottomInset != imeBottomInset) {
             val titleView: TextView? = findViewById(R.id.title)
             if (titleView != null) {
@@ -61,8 +64,14 @@
                 }
             }
         }
-        setPadding(paddingLeft, paddingTop, paddingRight, imeBottomInset)
-        return insets.inset(0, 0, 0, imeBottomInset)
+
+        setPadding(
+            0,
+            statusBarInsets.top,
+            0,
+            if (keyboardInsets.bottom == 0) navigationInsets.bottom else keyboardInsets.bottom
+        )
+        return WindowInsets.CONSUMED
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
index 75331f0..1086897 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
@@ -1,7 +1,11 @@
 package com.android.systemui.biometrics.ui
 
 import android.content.Context
+import android.graphics.Insets
 import android.util.AttributeSet
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
 import android.widget.LinearLayout
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
@@ -9,7 +13,7 @@
 
 /** Pattern credential view for BiometricPrompt. */
 class CredentialPatternView(context: Context, attrs: AttributeSet?) :
-    LinearLayout(context, attrs), CredentialView {
+    LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener {
 
     /** Initializes the view. */
     override fun init(
@@ -20,4 +24,17 @@
     ) {
         CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
     }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        setOnApplyWindowInsetsListener(this)
+    }
+
+    override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
+        val statusBarInsets: Insets = insets.getInsets(Type.statusBars())
+        val navigationInsets: Insets = insets.getInsets(Type.navigationBars())
+
+        setPadding(0, statusBarInsets.top, 0, navigationInsets.bottom)
+        return WindowInsets.CONSUMED
+    }
 }
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 c29efc0..ac48b6a 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
@@ -38,11 +38,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.AuthBiometricFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthIconController
+import com.airbnb.lottie.LottieCompositionFactory
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -56,6 +52,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -101,10 +98,15 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         descriptionView.movementMethod = ScrollingMovementMethod()
 
-        val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+        val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
 
-        PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel)
+        PromptIconViewBinder.bind(
+            iconView,
+            iconOverlayView,
+            view.getUpdatedFingerprintAffordanceSize(),
+            viewModel.iconViewModel
+        )
 
         val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
@@ -128,9 +130,21 @@
 
         // bind to prompt
         var boundSize = false
+
         view.repeatWhenAttached {
             // these do not change and need to be set before any size transitions
             val modalities = viewModel.modalities.first()
+            if (modalities.hasFingerprint) {
+                /**
+                 * Load the given [rawResources] immediately so they are cached for use in the
+                 * [context].
+                 */
+                val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
+                for (res in rawResources) {
+                    LottieCompositionFactory.fromRawRes(view.context, res)
+                }
+            }
+
             titleView.text = viewModel.title.first()
             descriptionView.text = viewModel.description.first()
             subtitleView.text = viewModel.subtitle.first()
@@ -148,27 +162,8 @@
                 legacyCallback.onButtonTryAgain()
             }
 
-            // TODO(b/251476085): migrate legacy icon controllers and remove
-            var legacyState = viewModel.legacyState.value
-            val iconController =
-                modalities.asIconController(
-                    view.context,
-                    iconView,
-                    iconViewOverlay,
-                )
-            adapter.attach(this, iconController, modalities, legacyCallback)
-            if (iconController is AuthBiometricFingerprintIconController) {
-                view.updateFingerprintAffordanceSize(iconController)
-            }
-            if (iconController is HackyCoexIconController) {
-                iconController.faceMode = !viewModel.isConfirmationRequired.first()
-            }
+            adapter.attach(this, modalities, legacyCallback)
 
-            // the icon controller must be created before this happens for the legacy
-            // sizing code in BiometricPromptLayout to work correctly. Simplify this
-            // when those are also migrated. (otherwise the icon size may not be set to
-            // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
-            // used as part of the measure spec)
             if (!boundSize) {
                 boundSize = true
                 BiometricViewSizeBinder.bind(
@@ -212,14 +207,6 @@
                     ) {
                         legacyCallback.onStartDelayedFingerprintSensor()
                     }
-
-                    if (newMode.isStarted) {
-                        // do wonky switch from implicit to explicit flow
-                        (iconController as? HackyCoexIconController)?.faceMode = false
-                        viewModel.showAuthenticating(
-                            modalities.asDefaultHelpMessage(view.context),
-                        )
-                    }
                 }
             }
 
@@ -312,7 +299,7 @@
                     viewModel.isIconConfirmButton
                         .map { isPending ->
                             when {
-                                isPending && iconController.actsAsConfirmButton ->
+                                isPending && modalities.hasFaceAndFingerprint ->
                                     View.OnTouchListener { _: View, event: MotionEvent ->
                                         viewModel.onOverlayTouch(event)
                                     }
@@ -320,22 +307,11 @@
                             }
                         }
                         .collect { onTouch ->
-                            iconViewOverlay.setOnTouchListener(onTouch)
+                            iconOverlayView.setOnTouchListener(onTouch)
                             iconView.setOnTouchListener(onTouch)
                         }
                 }
 
-                // TODO(b/251476085): remove w/ legacy icon controllers
-                // set icon affordance using legacy states
-                // like the old code, this causes animations to repeat on config changes :(
-                // but keep behavior for now as no one has complained...
-                launch {
-                    viewModel.legacyState.collect { newState ->
-                        iconController.updateState(legacyState, newState)
-                        legacyState = newState
-                    }
-                }
-
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
@@ -350,7 +326,7 @@
 
                             // Allow icon to be used as confirmation button with a11y enabled
                             if (accessibilityManager.isTouchExplorationEnabled) {
-                                iconViewOverlay.setOnClickListener {
+                                iconOverlayView.setOnClickListener {
                                     viewModel.confirmAuthenticated()
                                 }
                                 iconView.setOnClickListener { viewModel.confirmAuthenticated() }
@@ -377,7 +353,6 @@
                 launch {
                     viewModel.message.collect { promptMessage ->
                         val isError = promptMessage is PromptMessage.Error
-
                         indicatorMessageView.text = promptMessage.message
                         indicatorMessageView.setTextColor(
                             if (isError) textColorError else textColorHint
@@ -472,9 +447,6 @@
     private var modalities: BiometricModalities = BiometricModalities()
     private var legacyCallback: Callback? = null
 
-    var legacyIconController: AuthIconController? = null
-        private set
-
     // hacky way to suppress lockout errors
     private val lockoutErrorStrings =
         listOf(
@@ -485,24 +457,20 @@
 
     fun attach(
         lifecycleOwner: LifecycleOwner,
-        iconController: AuthIconController,
         activeModalities: BiometricModalities,
         callback: Callback,
     ) {
         modalities = activeModalities
-        legacyIconController = iconController
         legacyCallback = callback
 
         lifecycleOwner.lifecycle.addObserver(
             object : DefaultLifecycleObserver {
                 override fun onCreate(owner: LifecycleOwner) {
                     lifecycleScope = owner.lifecycleScope
-                    iconController.deactivated = false
                 }
 
                 override fun onDestroy(owner: LifecycleOwner) {
                     lifecycleScope = null
-                    iconController.deactivated = true
                 }
             }
         )
@@ -626,61 +594,9 @@
         else -> ""
     }
 
-private fun BiometricModalities.asIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-): AuthIconController =
-    when {
-        hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
-        hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-        hasFace -> AuthBiometricFaceIconController(context, iconView)
-        else -> throw IllegalStateException("unexpected view type :$this")
-    }
-
 private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
 
 private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
 
 // TODO(b/251476085): proper type?
 typealias BiometricJankListener = Animator.AnimatorListener
-
-// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
-private class HackyCoexIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
-
-    private var state: Spaghetti.BiometricState? = null
-    private val faceController = AuthBiometricFaceIconController(context, iconView)
-
-    var faceMode: Boolean = true
-        set(value) {
-            if (field != value) {
-                field = value
-
-                faceController.deactivated = !value
-                iconView.setImageIcon(null)
-                iconViewOverlay.setImageIcon(null)
-                state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
-            }
-        }
-
-    override fun updateIcon(
-        lastState: Spaghetti.BiometricState,
-        newState: Spaghetti.BiometricState,
-    ) {
-        if (deactivated) {
-            return
-        }
-
-        if (faceMode) {
-            faceController.updateIcon(lastState, newState)
-        } else {
-            super.updateIcon(lastState, newState)
-        }
-
-        state = newState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
deleted file mode 100644
index d28f1dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.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.biometrics.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for [BiometricPromptLayout.iconView]. */
-object PromptFingerprintIconViewBinder {
-
-    /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */
-    @JvmStatic
-    fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) {
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                viewModel.onConfigurationChanged(view.context.resources.configuration)
-                launch {
-                    viewModel.iconAsset.collect { iconAsset ->
-                        if (iconAsset != -1) {
-                            view.setAnimation(iconAsset)
-                            // TODO: must replace call below once non-sfps asset logic and
-                            // shouldAnimateIconView logic is migrated to this ViewModel.
-                            view.playAnimation()
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
new file mode 100644
index 0000000..475ef18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.lifecycle.repeatWhenAttached
+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 kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.iconView]. */
+object PromptIconViewBinder {
+    /**
+     * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
+     * [PromptIconViewModel].
+     */
+    @JvmStatic
+    fun bind(
+        iconView: LottieAnimationView,
+        iconOverlayView: LottieAnimationView,
+        iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
+        viewModel: PromptIconViewModel
+    ) {
+        iconView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.onConfigurationChanged(iconView.context.resources.configuration)
+                if (iconViewLayoutParamSizeOverride != null) {
+                    iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+                    iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+
+                    iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+                    iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+                }
+
+                var faceIcon: AnimatedVectorDrawable? = null
+                val faceIconCallback =
+                    object : Animatable2.AnimationCallback() {
+                        override fun onAnimationStart(drawable: Drawable) {
+                            viewModel.onAnimationStart()
+                        }
+
+                        override fun onAnimationEnd(drawable: Drawable) {
+                            viewModel.onAnimationEnd()
+                        }
+                    }
+
+                launch {
+                    viewModel.activeAuthType.collect { activeAuthType ->
+                        if (iconViewLayoutParamSizeOverride == null) {
+                            val width: Int
+                            val height: Int
+                            when (activeAuthType) {
+                                AuthType.Fingerprint,
+                                AuthType.Coex -> {
+                                    width = viewModel.fingerprintIconWidth
+                                    height = viewModel.fingerprintIconHeight
+                                }
+                                AuthType.Face -> {
+                                    width = viewModel.faceIconWidth
+                                    height = viewModel.faceIconHeight
+                                }
+                            }
+
+                            iconView.layoutParams.width = width
+                            iconView.layoutParams.height = height
+
+                            iconOverlayView.layoutParams.width = width
+                            iconOverlayView.layoutParams.height = height
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.iconAsset
+                        .sample(
+                            combine(
+                                viewModel.activeAuthType,
+                                viewModel.shouldAnimateIconView,
+                                viewModel.shouldRepeatAnimation,
+                                viewModel.showingError,
+                                ::toQuad
+                            ),
+                            ::toQuint
+                        )
+                        .collect {
+                            (
+                                iconAsset,
+                                activeAuthType,
+                                shouldAnimateIconView,
+                                shouldRepeatAnimation,
+                                showingError) ->
+                            if (iconAsset != -1) {
+                                when (activeAuthType) {
+                                    AuthType.Fingerprint,
+                                    AuthType.Coex -> {
+                                        iconView.setAnimation(iconAsset)
+                                        iconView.frame = 0
+
+                                        if (shouldAnimateIconView) {
+                                            iconView.playAnimation()
+                                        }
+                                    }
+                                    AuthType.Face -> {
+                                        faceIcon?.apply {
+                                            unregisterAnimationCallback(faceIconCallback)
+                                            stop()
+                                        }
+                                        faceIcon =
+                                            iconView.context.getDrawable(iconAsset)
+                                                as AnimatedVectorDrawable
+                                        faceIcon?.apply {
+                                            iconView.setImageDrawable(this)
+                                            if (shouldAnimateIconView) {
+                                                forceAnimationOnUI()
+                                                if (shouldRepeatAnimation) {
+                                                    registerAnimationCallback(faceIconCallback)
+                                                }
+                                                start()
+                                            }
+                                        }
+                                    }
+                                }
+                                LottieColorUtils.applyDynamicColors(iconView.context, iconView)
+                                viewModel.setPreviousIconWasError(showingError)
+                            }
+                        }
+                }
+
+                launch {
+                    viewModel.iconOverlayAsset
+                        .sample(
+                            combine(
+                                viewModel.shouldAnimateIconOverlay,
+                                viewModel.showingError,
+                                ::Pair
+                            ),
+                            ::toTriple
+                        )
+                        .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
+                            if (iconOverlayAsset != -1) {
+                                iconOverlayView.setAnimation(iconOverlayAsset)
+                                iconOverlayView.frame = 0
+                                LottieColorUtils.applyDynamicColors(
+                                    iconOverlayView.context,
+                                    iconOverlayView
+                                )
+
+                                if (shouldAnimateIconOverlay) {
+                                    iconOverlayView.playAnimation()
+                                }
+                                viewModel.setPreviousIconOverlayWasError(showingError)
+                            }
+                        }
+                }
+
+                launch {
+                    viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
+                        if (shouldFlipIconView) {
+                            iconView.rotation = 180f
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.contentDescriptionId.collect { id ->
+                        if (id != -1) {
+                            iconView.contentDescription = iconView.context.getString(id)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
deleted file mode 100644
index dfd3a9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ /dev/null
@@ -1,95 +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.biometrics.ui.viewmodel
-
-import android.annotation.RawRes
-import android.content.res.Configuration
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.shared.model.DisplayRotation
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Models UI of [BiometricPromptLayout.iconView] */
-class PromptFingerprintIconViewModel
-@Inject
-constructor(
-    private val displayStateInteractor: DisplayStateInteractor,
-    promptSelectorInteractor: PromptSelectorInteractor,
-) {
-    /** Current BiometricPromptLayout.iconView asset. */
-    val iconAsset: Flow<Int> =
-        combine(
-            displayStateInteractor.currentRotation,
-            displayStateInteractor.isFolded,
-            displayStateInteractor.isInRearDisplayMode,
-            promptSelectorInteractor.sensorType,
-        ) {
-            rotation: DisplayRotation,
-            isFolded: Boolean,
-            isInRearDisplayMode: Boolean,
-            sensorType: FingerprintSensorType ->
-            when (sensorType) {
-                FingerprintSensorType.POWER_BUTTON ->
-                    getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode)
-                // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel
-                else -> -1
-            }
-        }
-
-    @RawRes
-    private fun getSideFpsAnimationAsset(
-        rotation: DisplayRotation,
-        isDeviceFolded: Boolean,
-        isInRearDisplayMode: Boolean,
-    ): Int =
-        when (rotation) {
-            DisplayRotation.ROTATION_90 ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_portrait_reverse_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_topleft
-                } else {
-                    R.raw.biometricprompt_portrait_base_topleft
-                }
-            DisplayRotation.ROTATION_270 ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_portrait_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_bottomright
-                } else {
-                    R.raw.biometricprompt_portrait_base_bottomright
-                }
-            else ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_landscape_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_default
-                } else {
-                    R.raw.biometricprompt_landscape_base
-                }
-        }
-
-    /** Called on configuration changes */
-    fun onConfigurationChanged(newConfig: Configuration) {
-        displayStateInteractor.onConfigurationChanged(newConfig)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
new file mode 100644
index 0000000..11a5d8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.annotation.DrawableRes
+import android.annotation.RawRes
+import android.content.res.Configuration
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+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
+
+/**
+ * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
+ */
+class PromptIconViewModel
+constructor(
+    promptViewModel: PromptViewModel,
+    private val displayStateInteractor: DisplayStateInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor
+) {
+
+    /** Auth types for the UI to display. */
+    enum class AuthType {
+        Fingerprint,
+        Face,
+        Coex
+    }
+
+    /**
+     * Indicates what auth type the UI currently displays.
+     * Fingerprint-only auth -> Fingerprint
+     * Face-only auth -> Face
+     * Co-ex auth, implicit flow -> Face
+     * Co-ex auth, explicit flow -> Coex
+     */
+    val activeAuthType: Flow<AuthType> =
+        combine(
+            promptViewModel.modalities.distinctUntilChanged(),
+            promptViewModel.faceMode.distinctUntilChanged()
+        ) { modalities, faceMode ->
+            if (modalities.hasFaceAndFingerprint && !faceMode) {
+                AuthType.Coex
+            } else if (modalities.hasFaceOnly || faceMode) {
+                AuthType.Face
+            } else if (modalities.hasFingerprintOnly) {
+                AuthType.Fingerprint
+            } else {
+                throw IllegalStateException("unexpected modality: $modalities")
+            }
+        }
+
+    /** Whether an error message is currently being shown. */
+    val showingError = promptViewModel.showingError
+
+    /** Whether the previous icon shown displayed an error. */
+    private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Whether the previous icon overlay shown displayed an error. */
+    private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    fun setPreviousIconWasError(previousIconWasError: Boolean) {
+        _previousIconWasError.value = previousIconWasError
+    }
+
+    fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
+        _previousIconOverlayWasError.value = previousIconOverlayWasError
+    }
+
+    /** Called when iconView begins animating. */
+    fun onAnimationStart() {
+        _animationEnded.value = false
+    }
+
+    /** Called when iconView ends animating. */
+    fun onAnimationEnd() {
+        _animationEnded.value = true
+    }
+
+    private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /**
+     * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+     * ended).
+     */
+    val shouldPulseAnimation: Flow<Boolean> =
+        combine(_animationEnded, promptViewModel.isAuthenticating) {
+                animationEnded,
+                isAuthenticating ->
+                animationEnded && isAuthenticating
+            }
+            .distinctUntilChanged()
+
+    private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+    val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
+    /** Layout params for fingerprint iconView */
+    val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
+    val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+
+    /** Layout params for face iconView */
+    val faceIconWidth: Int = promptViewModel.faceIconWidth
+    val faceIconHeight: Int = promptViewModel.faceIconHeight
+
+    /** Current BiometricPromptLayout.iconView asset. */
+    val iconAsset: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        displayStateInteractor.isFolded,
+                        displayStateInteractor.isInRearDisplayMode,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        rotation: DisplayRotation,
+                        isFolded: Boolean,
+                        isInRearDisplayMode: Boolean,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+                            else ->
+                                getFingerprintIconViewAsset(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                        }
+                    }
+                AuthType.Face ->
+                    shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+                        if (shouldPulseAnimation) {
+                            val iconAsset =
+                                if (_lastPulseLightToDark.value) {
+                                    R.drawable.face_dialog_pulse_dark_to_light
+                                } else {
+                                    R.drawable.face_dialog_pulse_light_to_dark
+                                }
+                            _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+                            flowOf(iconAsset)
+                        } else {
+                            combine(
+                                promptViewModel.isAuthenticated.distinctUntilChanged(),
+                                promptViewModel.isAuthenticating.distinctUntilChanged(),
+                                promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+                                promptViewModel.showingError.distinctUntilChanged()
+                            ) {
+                                authState: PromptAuthState,
+                                isAuthenticating: Boolean,
+                                isPendingConfirmation: Boolean,
+                                showingError: Boolean ->
+                                getFaceIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                            }
+                        }
+                    }
+                AuthType.Coex ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        displayStateInteractor.isFolded,
+                        displayStateInteractor.isInRearDisplayMode,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError,
+                    ) {
+                        rotation: DisplayRotation,
+                        isFolded: Boolean,
+                        isInRearDisplayMode: Boolean,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+                            else ->
+                                getCoexIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                        }
+                    }
+            }
+        }
+
+    private fun getFingerprintIconViewAsset(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isAuthenticated) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_success_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
+        } else if (showingError) {
+            R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+        } else {
+            -1
+        }
+
+    @RawRes
+    private fun getSfpsIconViewAsset(
+        rotation: DisplayRotation,
+        isDeviceFolded: Boolean,
+        isInRearDisplayMode: Boolean,
+    ): Int =
+        when (rotation) {
+            DisplayRotation.ROTATION_90 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_reverse_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_topleft
+                } else {
+                    R.raw.biometricprompt_portrait_base_topleft
+                }
+            DisplayRotation.ROTATION_270 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_bottomright
+                } else {
+                    R.raw.biometricprompt_portrait_base_bottomright
+                }
+            else ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_landscape_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_default
+                } else {
+                    R.raw.biometricprompt_landscape_base
+                }
+        }
+
+    @DrawableRes
+    private fun getFaceIconViewAsset(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticated && isPendingConfirmation) {
+            R.drawable.face_dialog_wink_from_dark
+        } else if (authState.isAuthenticated) {
+            R.drawable.face_dialog_dark_to_checkmark
+        } else if (isAuthenticating) {
+            _lastPulseLightToDark.value = false
+            R.drawable.face_dialog_pulse_dark_to_light
+        } else if (showingError) {
+            R.drawable.face_dialog_dark_to_error
+        } else if (_previousIconWasError.value) {
+            R.drawable.face_dialog_error_to_idle
+        } else {
+            R.drawable.face_dialog_idle_static
+        }
+
+    @RawRes
+    private fun getCoexIconViewAsset(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+            R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+        } else if (isPendingConfirmation) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_unlock_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
+            }
+        } else if (authState.isAuthenticated) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_success_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
+        } else if (showingError) {
+            R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+        } else {
+            -1
+        }
+
+    /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
+    var iconOverlayAsset: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        rotation: DisplayRotation,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconOverlayAsset(
+                                    rotation,
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else -> -1
+                        }
+                    }
+                AuthType.Face -> flowOf(-1)
+            }
+        }
+
+    @RawRes
+    private fun getSfpsIconOverlayAsset(
+        rotation: DisplayRotation,
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isAuthenticated) {
+            if (_previousIconOverlayWasError.value) {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_error_to_success_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_error_to_success_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
+                }
+            } else {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                }
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconOverlayWasError.value) {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+                }
+            } else {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_fingerprint_to_error_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_fingerprint_to_error_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                }
+            }
+        } else if (showingError) {
+            when (rotation) {
+                DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+                DisplayRotation.ROTATION_90 ->
+                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+                DisplayRotation.ROTATION_270 ->
+                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+            }
+        } else {
+            -1
+        }
+
+    /** Content description for iconView */
+    val contentDescriptionId: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        getFingerprintIconContentDescriptionId(
+                            sensorType,
+                            authState.isAuthenticated,
+                            isAuthenticating,
+                            isPendingConfirmation,
+                            showingError
+                        )
+                    }
+                AuthType.Face ->
+                    combine(
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError,
+                    ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+                        ->
+                        getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
+                    }
+            }
+        }
+
+    private fun getFingerprintIconContentDescriptionId(
+        sensorType: FingerprintSensorType,
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isPendingConfirmation) {
+            when (sensorType) {
+                FingerprintSensorType.POWER_BUTTON ->
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                else -> R.string.fingerprint_dialog_authenticated_confirmation
+            }
+        } else if (isAuthenticating || isAuthenticated) {
+            when (sensorType) {
+                FingerprintSensorType.POWER_BUTTON ->
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                else -> R.string.fingerprint_dialog_touch_sensor
+            }
+        } else if (showingError) {
+            R.string.biometric_dialog_try_again
+        } else {
+            -1
+        }
+
+    private fun getFaceIconContentDescriptionId(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+            R.string.biometric_dialog_face_icon_description_confirmed
+        } else if (authState.isAuthenticated) {
+            R.string.biometric_dialog_face_icon_description_authenticated
+        } else if (isAuthenticating) {
+            R.string.biometric_dialog_face_icon_description_authenticating
+        } else if (showingError) {
+            R.string.keyguard_face_failed
+        } else {
+            R.string.biometric_dialog_face_icon_description_idle
+        }
+
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
+    val shouldAnimateIconView: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else ->
+                                shouldAnimateFingerprintIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                        }
+                    }
+                AuthType.Face ->
+                    combine(
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+                        ->
+                        isAuthenticating ||
+                            authState.isAuthenticated ||
+                            showingError ||
+                            _previousIconWasError.value
+                    }
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError,
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else ->
+                                shouldAnimateCoexIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                        }
+                    }
+            }
+        }
+
+    private fun shouldAnimateFingerprintIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
+
+    private fun shouldAnimateSfpsIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = isAuthenticated || isAuthenticating || showingError
+
+    private fun shouldAnimateCoexIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ) =
+        (isAuthenticating && _previousIconWasError.value) ||
+            isPendingConfirmation ||
+            isAuthenticated ||
+            showingError
+
+    /** Whether the current iconOverlayAsset animation should be playing. */
+    val shouldAnimateIconOverlay: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconOverlay(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else -> false
+                        }
+                    }
+                AuthType.Face -> flowOf(false)
+            }
+        }
+
+    private fun shouldAnimateSfpsIconOverlay(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+
+    /** Whether the iconView should be flipped due to a device using reverse default rotation . */
+    val shouldFlipIconView: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        displayStateInteractor.currentRotation
+                    ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                (rotation == DisplayRotation.ROTATION_180)
+                            else -> false
+                        }
+                    }
+                AuthType.Face -> flowOf(false)
+            }
+        }
+
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+    val shouldRepeatAnimation: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex -> flowOf(false)
+                AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+            }
+        }
+
+    /** Called on configuration changes */
+    fun onConfigurationChanged(newConfig: Configuration) {
+        displayStateInteractor.onConfigurationChanged(newConfig)
+    }
+
+    /** iconView assets for caching */
+    fun getRawAssets(hasSfps: Boolean): List<Int> {
+        return if (hasSfps) {
+            listOf(
+                R.raw.biometricprompt_fingerprint_to_error_landscape,
+                R.raw.biometricprompt_folded_base_bottomright,
+                R.raw.biometricprompt_folded_base_default,
+                R.raw.biometricprompt_folded_base_topleft,
+                R.raw.biometricprompt_landscape_base,
+                R.raw.biometricprompt_portrait_base_bottomright,
+                R.raw.biometricprompt_portrait_base_topleft,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+                R.raw.biometricprompt_symbol_error_to_success_landscape,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+            )
+        } else {
+            listOf(
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+                R.raw.fingerprint_dialogue_error_to_success_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            )
+        }
+    }
+}
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 267afae..e49b4a7 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
@@ -28,10 +28,10 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
-import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -39,7 +39,6 @@
 import kotlinx.coroutines.delay
 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
@@ -51,25 +50,29 @@
 class PromptViewModel
 @Inject
 constructor(
-    private val displayStateInteractor: DisplayStateInteractor,
-    private val promptSelectorInteractor: PromptSelectorInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor,
     private val vibrator: VibratorHelper,
     @Application context: Context,
     private val featureFlags: FeatureFlags,
 ) {
-    /** Models UI of [BiometricPromptLayout.iconView] */
-    val fingerprintIconViewModel: PromptFingerprintIconViewModel =
-        PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
         promptSelectorInteractor.prompt
             .map { it?.modalities ?: BiometricModalities() }
             .distinctUntilChanged()
 
-    // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
-    private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
-    val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
+    /** Layout params for fingerprint iconView */
+    val fingerprintIconWidth: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width)
+    val fingerprintIconHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height)
+
+    /** Layout params for face iconView */
+    val faceIconWidth: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+    val faceIconHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
@@ -82,6 +85,12 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
+    /** If the auth is pending confirmation. */
+    val isPendingConfirmation: Flow<Boolean> =
+        isAuthenticated.map { authState ->
+            authState.isAuthenticated && authState.needsUserConfirmation
+        }
+
     private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** The kind of credential the user has. */
@@ -96,6 +105,9 @@
     /** A message to show the user, if there is an error, hint, or help to show. */
     val message: Flow<PromptMessage> = _message.asStateFlow()
 
+    /** Whether an error message is currently being shown. */
+    val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged()
+
     private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
 
     private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
@@ -141,6 +153,38 @@
             !isOverlayTouched && size.isNotSmall
         }
 
+    /**
+     * When fingerprint and face modalities are enrolled, indicates whether only face auth has
+     * started.
+     *
+     * True when fingerprint and face modalities are enrolled and implicit flow is active. This
+     * occurs in co-ex auth when confirmation is not required and only face auth is started, then
+     * becomes false when device transitions to explicit flow after a first error, when the
+     * fingerprint sensor is started.
+     *
+     * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but
+     * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled.
+     */
+    val faceMode: Flow<Boolean> =
+        combine(modalities, isConfirmationRequired, fingerprintStartMode) {
+                modalities: BiometricModalities,
+                isConfirmationRequired: Boolean,
+                fingerprintStartMode: FingerprintStartMode ->
+                if (modalities.hasFaceAndFingerprint) {
+                    if (isConfirmationRequired) {
+                        false
+                    } else {
+                        !fingerprintStartMode.isStarted
+                    }
+                } else {
+                    false
+                }
+            }
+            .distinctUntilChanged()
+
+    val iconViewModel: PromptIconViewModel =
+        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+
     /** Padding for prompt UI elements */
     val promptPadding: Flow<Rect> =
         combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -184,9 +228,9 @@
     val isConfirmButtonVisible: Flow<Boolean> =
         combine(
                 size,
-                isAuthenticated,
-            ) { size, authState ->
-                size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+                isPendingConfirmation,
+            ) { size, isPendingConfirmation ->
+                size.isNotSmall && isPendingConfirmation
             }
             .distinctUntilChanged()
 
@@ -293,7 +337,6 @@
         _isAuthenticated.value = PromptAuthState(false)
         _forceMediumSize.value = true
         _message.value = PromptMessage.Error(message)
-        _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
 
         if (hapticFeedback) {
             vibrator.error(failedModality)
@@ -305,7 +348,7 @@
             if (authenticateAfterError) {
                 showAuthenticating(messageAfterError)
             } else {
-                showInfo(messageAfterError)
+                showHelp(messageAfterError)
             }
         }
     }
@@ -325,15 +368,12 @@
     private fun supportsRetry(failedModality: BiometricModality) =
         failedModality == BiometricModality.Face
 
-    suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
-    suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
-
     /**
      * Show a persistent help message.
      *
      * Will be show even if the user has already authenticated.
      */
-    private suspend fun showHelp(message: String, clearIconError: Boolean) {
+    suspend fun showHelp(message: String) {
         val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
         if (!alreadyAuthenticated) {
             _isAuthenticating.value = false
@@ -343,16 +383,6 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value =
-            if (alreadyAuthenticated && isConfirmationRequired.first()) {
-                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-            } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
-                Spaghetti.BiometricState.STATE_AUTHENTICATED
-            } else if (clearIconError) {
-                Spaghetti.BiometricState.STATE_IDLE
-            } else {
-                Spaghetti.BiometricState.STATE_HELP
-            }
 
         messageJob?.cancel()
         messageJob = null
@@ -376,7 +406,6 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value = Spaghetti.BiometricState.STATE_HELP
 
         messageJob?.cancel()
         messageJob = launch {
@@ -396,7 +425,6 @@
         _isAuthenticating.value = true
         _isAuthenticated.value = PromptAuthState(false)
         _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
-        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
 
         // reset the try again button(s) after the user attempts a retry
         if (isRetry) {
@@ -427,12 +455,6 @@
         _isAuthenticated.value =
             PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
         _message.value = PromptMessage.Empty
-        _legacyState.value =
-            if (needsUserConfirmation) {
-                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-            } else {
-                Spaghetti.BiometricState.STATE_AUTHENTICATED
-            }
 
         if (!needsUserConfirmation) {
             vibrator.success(modality)
@@ -472,7 +494,6 @@
 
         _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
-        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
 
         vibrator.success(authState.authenticatedModality)
 
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 6e26fe9..56dfa5ed 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,7 +28,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.DejankUtils
-import com.android.systemui.res.R
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -41,8 +40,10 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -73,6 +74,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val trustRepository: TrustRepository,
     @Application private val applicationScope: CoroutineScope,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     private val passiveAuthBouncerDelay =
         context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -144,6 +146,16 @@
     /** Show the bouncer if necessary and set the relevant states. */
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
+        if (primaryBouncerView.delegate == null) {
+            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."
+            )
+            return
+        }
+
         // Reset some states as we show the bouncer.
         repository.setKeyguardAuthenticatedBiometrics(null)
         repository.setPrimaryStartingToHide(false)
@@ -384,7 +396,7 @@
     /** Returns whether the bouncer should be full screen. */
     private fun needsFullscreenBouncer(): Boolean {
         val mode: KeyguardSecurityModel.SecurityMode =
-            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+            keyguardSecurityModel.getSecurityMode(selectedUserInteractor.getSelectedUserId())
         return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
             mode == KeyguardSecurityModel.SecurityMode.SimPuk
     }
@@ -403,7 +415,7 @@
     /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
     private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
         val canRunFaceAuth =
-            keyguardStateController.isFaceAuthEnabled &&
+            keyguardStateController.isFaceEnrolled &&
                 keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
                 keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()
         val canRunActiveUnlock =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 36e5db4..ac3d4b6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -26,7 +26,6 @@
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityView
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
@@ -37,6 +36,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
@@ -53,6 +53,7 @@
         bouncerMessageInteractor: BouncerMessageInteractor,
         bouncerLogger: BouncerLogger,
         featureFlags: FeatureFlags,
+        selectedUserInteractor: SelectedUserInteractor,
     ) {
         // Builds the KeyguardSecurityContainerController from bouncer view group.
         val securityContainerController: KeyguardSecurityContainerController =
@@ -84,7 +85,7 @@
 
                 override fun showNextSecurityScreenOrFinish(): Boolean {
                     return securityContainerController.dismiss(
-                        KeyguardUpdateMonitor.getCurrentUser()
+                        selectedUserInteractor.getSelectedUserId()
                     )
                 }
 
@@ -220,7 +221,7 @@
                     launch {
                         viewModel.keyguardAuthenticated.collect {
                             securityContainerController.finish(
-                                KeyguardUpdateMonitor.getCurrentUser()
+                                selectedUserInteractor.getSelectedUserId()
                             )
                             viewModel.notifyKeyguardAuthenticated()
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index b268095..11c7a31 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -27,17 +27,16 @@
 import android.os.RemoteException
 import android.util.Log
 import android.view.WindowManager
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -59,7 +58,7 @@
     private val cameraIntents: CameraIntentsWrapper,
     private val contentResolver: ContentResolver,
     @Main private val uiExecutor: Executor,
-    private val userTracker: UserTracker
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /**
      * Whether the camera application can be launched for the camera launch gesture.
@@ -72,12 +71,12 @@
         val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
             getStartCameraIntent(),
             PackageManager.MATCH_DEFAULT_ONLY,
-            KeyguardUpdateMonitor.getCurrentUser()
+            selectedUserInteractor.getSelectedUserId()
         )
         val resolvedPackage = resolveInfo?.activityInfo?.packageName
         return (resolvedPackage != null &&
                 (statusBarState != StatusBarState.SHADE ||
-                !activityManager.isInForeground(resolvedPackage)))
+                        !activityManager.isInForeground(resolvedPackage)))
     }
 
     /**
@@ -89,7 +88,7 @@
         val intent: Intent = getStartCameraIntent()
         intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
         val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
-            intent, KeyguardUpdateMonitor.getCurrentUser()
+            intent, selectedUserInteractor.getSelectedUserId()
         )
         if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
             uiExecutor.execute {
@@ -102,7 +101,7 @@
                 val activityOptions = ActivityOptions.makeBasic()
                 activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
                 activityOptions.rotationAnimationHint =
-                    WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
                 try {
                     activityTaskManager.startActivityAsUser(
                         null,
@@ -116,7 +115,7 @@
                         Intent.FLAG_ACTIVITY_NEW_TASK,
                         null,
                         activityOptions.toBundle(),
-                        userTracker.userId,
+                        selectedUserInteractor.getSelectedUserId(true),
                     )
                 } catch (e: RemoteException) {
                     Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 39c01f7..a6b073d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -37,12 +37,15 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.sensors.ThresholdSensorEvent;
 import com.android.systemui.util.time.SystemClock;
 
+import dagger.Lazy;
+
 import java.util.Collections;
 
 import javax.inject.Inject;
@@ -66,6 +69,7 @@
     private final DockManager mDockManager;
     private final DelayableExecutor mMainExecutor;
     private final SystemClock mSystemClock;
+    private final Lazy<SelectedUserInteractor> mUserInteractor;
 
     private int mState;
     private boolean mShowingAod;
@@ -93,7 +97,7 @@
                 public void onBiometricAuthenticated(int userId,
                         BiometricSourceType biometricSourceType,
                         boolean isStrongBiometric) {
-                    if (userId == KeyguardUpdateMonitor.getCurrentUser()
+                    if (userId == mUserInteractor.get().getSelectedUserId()
                             && biometricSourceType == BiometricSourceType.FACE) {
                         mFalsingDataProvider.setJustUnlockedWithFace(true);
                     }
@@ -136,7 +140,8 @@
             BatteryController batteryController,
             DockManager dockManager,
             @Main DelayableExecutor mainExecutor,
-            SystemClock systemClock) {
+            SystemClock systemClock,
+            Lazy<SelectedUserInteractor> userInteractor) {
         mFalsingDataProvider = falsingDataProvider;
         mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -148,6 +153,7 @@
         mDockManager = dockManager;
         mMainExecutor = mainExecutor;
         mSystemClock = systemClock;
+        mUserInteractor = userInteractor;
 
         mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
         mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index bf4fba8..7d73896 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -19,18 +19,19 @@
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Color;
 import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.types.ExtractionType;
 import com.android.internal.colorextraction.types.Tonal;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+
+import dagger.Lazy;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -45,22 +46,23 @@
         ConfigurationController.ConfigurationListener {
     private static final String TAG = "SysuiColorExtractor";
     private final Tonal mTonal;
-    private boolean mHasMediaArtwork;
     private final GradientColors mNeutralColorsLock;
-    private final GradientColors mBackdropColors;
+    private Lazy<SelectedUserInteractor> mUserInteractor;
 
     @Inject
     public SysuiColorExtractor(
             Context context,
             ConfigurationController configurationController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            Lazy<SelectedUserInteractor> userInteractor) {
         this(
                 context,
                 new Tonal(context),
                 configurationController,
                 context.getSystemService(WallpaperManager.class),
                 dumpManager,
-                false /* immediately */);
+                false /* immediately */,
+                userInteractor);
     }
 
     @VisibleForTesting
@@ -70,15 +72,14 @@
             ConfigurationController configurationController,
             WallpaperManager wallpaperManager,
             DumpManager dumpManager,
-            boolean immediately) {
+            boolean immediately,
+            Lazy<SelectedUserInteractor> userInteractor) {
         super(context, type, immediately, wallpaperManager);
         mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context);
         mNeutralColorsLock = new GradientColors();
         configurationController.addCallback(this);
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
-        mBackdropColors = new GradientColors();
-        mBackdropColors.setMainColor(Color.BLACK);
+        mUserInteractor = userInteractor;
 
         // Listen to all users instead of only the current one.
         if (wallpaperManager.isWallpaperSupported()) {
@@ -100,7 +101,7 @@
 
     @Override
     public void onColorsChanged(WallpaperColors colors, int which, int userId) {
-        if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+        if (userId != mUserInteractor.get().getSelectedUserId()) {
             // Colors do not belong to current user, ignoring.
             return;
         }
@@ -116,14 +117,6 @@
         triggerColorsChanged(WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
     }
 
-    @Override
-    public GradientColors getColors(int which, int type) {
-        if (mHasMediaArtwork && (which & WallpaperManager.FLAG_LOCK) != 0) {
-            return mBackdropColors;
-        }
-        return super.getColors(which, type);
-    }
-
     /**
      * Colors that should be using for scrims.
      *
@@ -133,14 +126,7 @@
      * - Black otherwise
      */
     public GradientColors getNeutralColors() {
-        return mHasMediaArtwork ? mBackdropColors : mNeutralColorsLock;
-    }
-
-    public void setHasMediaArtwork(boolean hasBackdrop) {
-        if (mHasMediaArtwork != hasBackdrop) {
-            mHasMediaArtwork = hasBackdrop;
-            triggerColorsChanged(WallpaperManager.FLAG_LOCK);
-        }
+        return mNeutralColorsLock;
     }
 
     @Override
@@ -157,7 +143,5 @@
         pw.println("    system: " + Arrays.toString(system));
         pw.println("    lock: " + Arrays.toString(lock));
         pw.println("  Neutral colors: " + mNeutralColorsLock);
-        pw.println("  Has media backdrop: " + mHasMediaArtwork);
-
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
new file mode 100644
index 0000000..7bca86e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+package com.android.systemui.common.ui
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.AttrRes
+import androidx.annotation.ColorInt
+import androidx.annotation.DimenRes
+import androidx.annotation.LayoutRes
+import com.android.settingslib.Utils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onThemeChanged
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.view.bindLatest
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Configuration-aware-state-tracking utilities. */
+class ConfigurationState
+@Inject
+constructor(
+    private val configurationController: ConfigurationController,
+    @Application private val context: Context,
+    private val layoutInflater: LayoutInflater,
+) {
+    /**
+     * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
+     * configuration.
+     *
+     * @see android.content.res.Resources.getDimensionPixelSize
+     */
+    fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
+        return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
+            context.resources.getDimensionPixelSize(id)
+        }
+    }
+
+    /**
+     * Returns a [Flow] that emits a color that is kept in sync with the device theme.
+     *
+     * @see Utils.getColorAttrDefaultColor
+     */
+    fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
+        return configurationController.onThemeChanged.emitOnStart().map {
+            Utils.getColorAttrDefaultColor(context, id, defaultValue)
+        }
+    }
+
+    /**
+     * Returns a [Flow] that emits a [View] that is re-inflated as necessary to remain in sync with
+     * the device configuration.
+     *
+     * @see LayoutInflater.inflate
+     */
+    @Suppress("UNCHECKED_CAST")
+    fun <T : View> inflateLayout(
+        @LayoutRes id: Int,
+        root: ViewGroup?,
+        attachToRoot: Boolean,
+    ): Flow<T> {
+        // TODO(b/305930747): This may lead to duplicate invocations if both flows emit, find a
+        //  solution to only emit one event.
+        return merge(
+                configurationController.onThemeChanged,
+                configurationController.onDensityOrFontScaleChanged,
+            )
+            .emitOnStart()
+            .map { layoutInflater.inflate(id, root, attachToRoot) as T }
+    }
+}
+
+/**
+ * Perform an inflation right away, then re-inflate whenever the device configuration changes, and
+ * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by
+ * [onInflate] when done.
+ *
+ * This never completes unless cancelled, it just suspends and waits for updates.
+ *
+ * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
+ *
+ * An example use-case of this is when a view needs to be re-inflated whenever a configuration
+ * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the
+ * code in the parent view's binder would look like:
+ * ```
+ * parentView.repeatWhenAttached {
+ *     configurationState
+ *         .reinflateOnChange(
+ *             R.layout.my_layout,
+ *             parentView,
+ *             attachToRoot = false,
+ *             coroutineScope = lifecycleScope,
+ *             configurationController.onThemeChanged,
+ *         ) { view: ChildView ->
+ *             ChildViewBinder.bind(view, childViewModel)
+ *         }
+ * }
+ * ```
+ *
+ * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
+ * [DisposableHandle].
+ */
+suspend fun <T : View> ConfigurationState.reinflateAndBindLatest(
+    @LayoutRes resource: Int,
+    root: ViewGroup?,
+    attachToRoot: Boolean,
+    onInflate: (T) -> DisposableHandle?,
+) {
+    inflateLayout<T>(resource, root, attachToRoot).bindLatest(onInflate)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
new file mode 100644
index 0000000..b0e6931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.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.common.ui.data
+
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule
+import dagger.Module
+
+@Module(includes = [ConfigurationRepositoryModule::class]) object CommonUiDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index b8de8d8..e449274 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -13,18 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
+@file:OptIn(ExperimentalCoroutinesApi::class)
 
 package com.android.systemui.common.ui.data.repository
 
 import android.content.Context
 import android.content.res.Configuration
 import android.view.DisplayInfo
+import androidx.annotation.DimenRes
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import dagger.Binds
+import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,7 +52,6 @@
     fun getDimensionPixelSize(id: Int): Int
 }
 
-@ExperimentalCoroutinesApi
 @SysUISingleton
 class ConfigurationRepositoryImpl
 @Inject
@@ -119,7 +122,12 @@
         return 1f
     }
 
-    override fun getDimensionPixelSize(id: Int): Int {
+    override fun getDimensionPixelSize(@DimenRes id: Int): Int {
         return context.resources.getDimensionPixelSize(id)
     }
 }
+
+@Module
+interface ConfigurationRepositoryModule {
+    @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 323070a..0781451 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@
 import android.util.AttributeSet
 import android.view.MotionEvent
 import android.view.View
+import android.view.ViewConfiguration
 import com.android.systemui.shade.TouchLogger
 import kotlin.math.pow
 import kotlin.math.sqrt
@@ -36,11 +37,18 @@
 class LongPressHandlingView(
     context: Context,
     attrs: AttributeSet?,
+    private val longPressDuration: () -> Long,
 ) :
     View(
         context,
         attrs,
     ) {
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+    ) : this(context, attrs, { ViewConfiguration.getLongPressTimeout().toLong() })
+
     interface Listener {
         /** Notifies that a long-press has been detected by the given view. */
         fun onLongPressDetected(
@@ -77,6 +85,7 @@
                 )
             },
             onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
+            longPressDuration = longPressDuration,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
index c2d4d12..a742e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
@@ -33,6 +33,8 @@
     private val onLongPressDetected: (x: Int, y: Int) -> Unit,
     /** Callback reporting the a single tap gesture was detected at the given coordinates. */
     private val onSingleTapDetected: () -> Unit,
+    /** Time for the touch to be considered a long-press in ms */
+    private val longPressDuration: () -> Long,
 ) {
     sealed class MotionEventModel {
         object Other : MotionEventModel()
@@ -77,7 +79,7 @@
                 cancelScheduledLongPress()
                 if (
                     event.distanceMoved <= ViewConfiguration.getTouchSlop() &&
-                        event.gestureDuration < ViewConfiguration.getLongPressTimeout()
+                        event.gestureDuration < longPressDuration()
                 ) {
                     dispatchSingleTap()
                 }
@@ -103,7 +105,7 @@
                         y = y,
                     )
                 },
-                ViewConfiguration.getLongPressTimeout().toLong(),
+                longPressDuration(),
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
new file mode 100644
index 0000000..b8e2de4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.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.communal.dagger
+
+import com.android.systemui.communal.data.repository.CommunalRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import dagger.Module
+
+@Module(
+    includes =
+        [
+            CommunalRepositoryModule::class,
+            CommunalTutorialRepositoryModule::class,
+            CommunalWidgetRepositoryModule::class,
+        ]
+)
+class CommunalModule
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
new file mode 100644
index 0000000..1a214ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.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.communal.data.model
+
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/** Metadata for the default widgets */
+data class CommunalWidgetMetadata(
+    /* Widget provider component name */
+    val componentName: String,
+
+    /* Defines the order in which the widget will be rendered in the grid. */
+    val priority: Int,
+
+    /* Supported sizes */
+    val sizes: List<CommunalContentSize>
+)
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 f6e0296..485e512 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
@@ -1,5 +1,6 @@
 package com.android.systemui.communal.data.repository
 
+import com.android.systemui.FeatureFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -15,7 +16,11 @@
 class CommunalRepositoryImpl
 @Inject
 constructor(
-    featureFlags: FeatureFlagsClassic,
+    private val featureFlags: FeatureFlags,
+    private val featureFlagsClassic: FeatureFlagsClassic,
 ) : CommunalRepository {
-    override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED)
+    override val isCommunalEnabled: Boolean
+        get() =
+            featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
+                featureFlags.communalHub()
 }
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
new file mode 100644
index 0000000..9a9b0e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.communal.data.repository
+
+import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+import android.provider.Settings.Secure.HubModeTutorialState
+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.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
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for the current state of hub mode tutorial. Valid states are defined in
+ * [HubModeTutorialState].
+ */
+interface CommunalTutorialRepository {
+    /** Emits the tutorial state stored in Settings */
+    val tutorialSettingState: StateFlow<Int>
+
+    /** Update the tutorial state */
+    suspend fun setTutorialState(@HubModeTutorialState state: Int)
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalTutorialRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    userRepository: UserRepository,
+    private val secureSettings: SecureSettings,
+    private val userTracker: UserTracker,
+    @CommunalLog logBuffer: LogBuffer,
+) : CommunalTutorialRepository {
+
+    companion object {
+        private const val TAG = "CommunalTutorialRepository"
+    }
+
+    private data class SettingsState(
+        @HubModeTutorialState val hubModeTutorialState: Int? = null,
+    )
+
+    private val logger = Logger(logBuffer, TAG)
+
+    private val settingsState: Flow<SettingsState> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { observeSettings() }
+            .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+    /** Emits the state of tutorial state in settings */
+    override val tutorialSettingState: StateFlow<Int> =
+        settingsState
+            .map { it.hubModeTutorialState }
+            .filterNotNull()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED
+            )
+
+    private fun observeSettings(): Flow<SettingsState> =
+        secureSettings
+            .observerFlow(
+                userId = userTracker.userId,
+                names =
+                    arrayOf(
+                        Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+                    )
+            )
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { readFromSettings() }
+
+    private suspend fun readFromSettings(): SettingsState =
+        withContext(backgroundDispatcher) {
+            val userId = userTracker.userId
+            val hubModeTutorialState =
+                secureSettings.getIntForUser(
+                    Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+                    HUB_MODE_TUTORIAL_NOT_STARTED,
+                    userId,
+                )
+            val settingsState = SettingsState(hubModeTutorialState)
+            logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
+                int1 = userId
+                str1 = settingsState.hubModeTutorialState.toString()
+            }
+
+            settingsState
+        }
+
+    override suspend fun setTutorialState(state: Int): Unit =
+        withContext(backgroundDispatcher) {
+            val userId = userTracker.userId
+            if (tutorialSettingState.value == state) {
+                return@withContext
+            }
+            logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
+                int1 = state
+                int2 = userId
+            }
+            secureSettings.putIntForUser(
+                Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+                state,
+                userId,
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
copy to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
index 9b0c3fa..69b0a27 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
@@ -12,14 +12,16 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.systemui.common.ui.data.repository
+package com.android.systemui.communal.data.repository
 
 import dagger.Binds
 import dagger.Module
 
 @Module
-interface CommonRepositoryModule {
-    @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+interface CommunalTutorialRepositoryModule {
+    @Binds
+    fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): 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 e2a7d07..77025dc 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
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.BroadcastReceiver
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
@@ -27,95 +28,125 @@
 import android.os.UserManager
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates the state of widgets for communal mode. */
 interface CommunalWidgetRepository {
     /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */
     val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?>
+
+    /** Widgets that are allowed to render in the glanceable hub */
+    val communalWidgetAllowlist: List<CommunalWidgetMetadata>
+
+    /** A flow of information about all the communal widgets to show. */
+    val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 }
 
 @SysUISingleton
 class CommunalWidgetRepositoryImpl
 @Inject
 constructor(
+    @Application private val applicationContext: Context,
     private val appWidgetManager: AppWidgetManager,
     private val appWidgetHost: AppWidgetHost,
     broadcastDispatcher: BroadcastDispatcher,
+    communalRepository: CommunalRepository,
     private val packageManager: PackageManager,
     private val userManager: UserManager,
     private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
-    featureFlags: FeatureFlags,
+    featureFlags: FeatureFlagsClassic,
 ) : CommunalWidgetRepository {
     companion object {
         const val TAG = "CommunalWidgetRepository"
         const val WIDGET_LABEL = "Stopwatch"
     }
+    override val communalWidgetAllowlist: List<CommunalWidgetMetadata>
 
     private val logger = Logger(logBuffer, TAG)
 
     // Whether the [AppWidgetHost] is listening for updates.
     private var isHostListening = false
 
+    init {
+        communalWidgetAllowlist =
+            if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList()
+    }
+
     // Widgets that should be rendered in communal mode.
     private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
 
-    private val isUserUnlocked: Flow<Boolean> = callbackFlow {
-        if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
-            awaitClose()
-        }
-
-        fun isUserUnlockingOrUnlocked(): Boolean {
-            return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
-        }
-
-        fun send() {
-            trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
-        }
-
-        if (isUserUnlockingOrUnlocked()) {
-            send()
-            awaitClose()
-        } else {
-            val receiver =
-                object : BroadcastReceiver() {
-                    override fun onReceive(context: Context?, intent: Intent?) {
-                        send()
-                    }
+    private val isUserUnlocked: Flow<Boolean> =
+        callbackFlow {
+                if (!communalRepository.isCommunalEnabled) {
+                    awaitClose()
                 }
 
-            broadcastDispatcher.registerReceiver(
-                receiver,
-                IntentFilter(Intent.ACTION_USER_UNLOCKED),
-            )
+                fun isUserUnlockingOrUnlocked(): Boolean {
+                    return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
+                }
 
-            awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                fun send() {
+                    trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+                }
+
+                if (isUserUnlockingOrUnlocked()) {
+                    send()
+                    awaitClose()
+                } else {
+                    val receiver =
+                        object : BroadcastReceiver() {
+                            override fun onReceive(context: Context?, intent: Intent?) {
+                                send()
+                            }
+                        }
+
+                    broadcastDispatcher.registerReceiver(
+                        receiver,
+                        IntentFilter(Intent.ACTION_USER_UNLOCKED),
+                    )
+
+                    awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                }
+            }
+            .distinctUntilChanged()
+
+    private val isHostActive: Flow<Boolean> =
+        isUserUnlocked.map {
+            if (it) {
+                startListening()
+                true
+            } else {
+                stopListening()
+                clearWidgets()
+                false
+            }
         }
-    }
 
     override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
-        isUserUnlocked.map { isUserUnlocked ->
-            if (!isUserUnlocked) {
-                clearWidgets()
-                stopListening()
+        isHostActive.map { isHostActive ->
+            if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
                 return@map null
             }
 
-            startListening()
-
             val providerInfo =
                 appWidgetManager.installedProviders.find {
                     it.loadLabel(packageManager).equals(WIDGET_LABEL)
@@ -129,6 +160,54 @@
             return@map addWidget(providerInfo)
         }
 
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+        isHostActive.map { isHostActive ->
+            if (!isHostActive) {
+                return@map emptyList()
+            }
+
+            // The allowlist should be fetched from the local database with all the metadata tied to
+            // a widget, including an appWidgetId if it has been bound. Before the database is set
+            // up, we are going to use the app widget host as the source of truth for bound widgets,
+            // and rebind each time on boot.
+
+            // Remove all previously bound widgets.
+            appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
+
+            val inventory = mutableListOf<CommunalWidgetContentModel>()
+
+            // Bind all widgets from the allowlist.
+            communalWidgetAllowlist.forEach {
+                val id = appWidgetHost.allocateAppWidgetId()
+                appWidgetManager.bindAppWidgetId(
+                    id,
+                    ComponentName.unflattenFromString(it.componentName),
+                )
+
+                inventory.add(
+                    CommunalWidgetContentModel(
+                        appWidgetId = id,
+                        providerInfo = appWidgetManager.getAppWidgetInfo(id),
+                        priority = it.priority,
+                    )
+                )
+            }
+
+            return@map inventory.toList()
+        }
+
+    private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
+        val componentNames =
+            applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
+        return componentNames.mapIndexed { index, name ->
+            CommunalWidgetMetadata(
+                componentName = name,
+                priority = componentNames.size - index,
+                sizes = listOf(CommunalContentSize.HALF),
+            )
+        }
+    }
+
     private fun startListening() {
         if (isHostListening) {
             return
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 9fb8da3..6238707 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
@@ -18,7 +18,8 @@
 
 import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -28,12 +29,22 @@
 class CommunalInteractor
 @Inject
 constructor(
-    communalRepository: CommunalRepository,
+    private val communalRepository: CommunalRepository,
     widgetRepository: CommunalWidgetRepository,
 ) {
+
     /** Whether communal features are enabled. */
-    val isCommunalEnabled: Boolean = communalRepository.isCommunalEnabled
+    val isCommunalEnabled: Boolean
+        get() = communalRepository.isCommunalEnabled
 
     /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
     val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
+
+    /**
+     * A flow of information about widgets to be shown in communal hub.
+     *
+     * Currently only showing persistent widgets that have been bound to the app widget service
+     * (have an allocated id).
+     */
+    val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
 }
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
new file mode 100644
index 0000000..276df4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import android.provider.Settings
+import com.android.systemui.communal.data.repository.CommunalTutorialRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Encapsulates business-logic related to communal tutorial state. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalTutorialInteractor
+@Inject
+constructor(
+    communalTutorialRepository: CommunalTutorialRepository,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    /** An observable for whether the tutorial is available. */
+    val isTutorialAvailable: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isKeyguardVisible,
+                communalTutorialRepository.tutorialSettingState,
+            ) { isKeyguardVisible, tutorialSettingState ->
+                isKeyguardVisible &&
+                    tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
index 0803a01..109ed2d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
index 0803a01..7f05b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -12,15 +12,14 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared
+package com.android.systemui.communal.shared.model
 
-import android.appwidget.AppWidgetProviderInfo
+enum class CommunalContentCategory {
+    /** The content persists in the communal hub until removed by the user. */
+    PERSISTENT,
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+    /** The content temporarily shows up in the communal hub when certain conditions are met. */
+    TRANSIENT,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 0803a01..39a6476 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -12,15 +12,18 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared
+package com.android.systemui.communal.shared.model
 
-import android.appwidget.AppWidgetProviderInfo
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+    /** Content takes the full height of the column. */
+    FULL,
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+    /** Content takes half of the height of the column. */
+    HALF,
+
+    /** Content takes a third of the height of the column. */
+    THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
similarity index 80%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0803a01..e141dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -12,15 +12,15 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared
+package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
+/** Encapsulates data for a communal widget. */
+data class CommunalWidgetContentModel(
     val appWidgetId: Int,
+    val providerInfo: AppWidgetProviderInfo,
+    val priority: Int,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
index 2a08d7f..0daf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
@@ -20,7 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.util.SizeF
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
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
new file mode 100644
index 0000000..dab6819
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.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
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** View binder for communal tutorial indicator shown on keyguard. */
+object CommunalTutorialIndicatorViewBinder {
+    fun bind(
+        view: TextView,
+        viewModel: CommunalTutorialIndicatorViewModel,
+    ): DisposableHandle {
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.showIndicator.collect { isVisible ->
+                            updateView(
+                                view = view,
+                                isIndicatorVisible = isVisible,
+                            )
+                        }
+                    }
+                }
+            }
+
+        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/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
new file mode 100644
index 0000000..98060dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.communal.ui.model
+
+import android.view.View
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/**
+ * Encapsulates data for a communal content that holds a view.
+ *
+ * This model stays in the UI layer.
+ */
+data class CommunalContentUiModel(
+    val view: View,
+    val size: CommunalContentSize,
+    val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index 3ff1f09..d8d1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.view.layout.blueprints
 
+import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection
 import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
@@ -28,10 +29,15 @@
 class DefaultCommunalBlueprint
 @Inject
 constructor(
+    defaultCommunalHubSection: DefaultCommunalHubSection,
     defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
 ) : KeyguardBlueprint {
     override val id: String = COMMUNAL
-    override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection)
+    override val sections: Set<KeyguardSection> =
+        setOf(
+            defaultCommunalHubSection,
+            defaultCommunalWidgetSection,
+        )
 
     companion object {
         const val COMMUNAL = "communal"
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
new file mode 100644
index 0000000..027cc96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.ui.view.layout.sections
+
+import android.content.res.Resources
+import android.graphics.Typeface
+import android.graphics.Typeface.NORMAL
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.removeView
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class CommunalTutorialIndicatorSection
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val communalTutorialIndicatorViewModel: CommunalTutorialIndicatorViewModel,
+    private val communalInteractor: CommunalInteractor,
+) : KeyguardSection() {
+    private var communalTutorialIndicatorHandle: DisposableHandle? = null
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!communalInteractor.isCommunalEnabled) {
+            return
+        }
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.communal_tutorial_indicator_padding
+            )
+        val view =
+            TextView(constraintLayout.context).apply {
+                id = R.id.communal_tutorial_indicator
+                visibility = View.GONE
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                gravity = Gravity.CENTER_VERTICAL
+                typeface = Typeface.create("google-sans", NORMAL)
+                text = constraintLayout.context.getString(R.string.communal_tutorial_indicator_text)
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!communalInteractor.isCommunalEnabled) {
+            return
+        }
+        communalTutorialIndicatorHandle =
+            CommunalTutorialIndicatorViewBinder.bind(
+                constraintLayout.requireViewById(R.id.communal_tutorial_indicator),
+                communalTutorialIndicatorViewModel,
+            )
+    }
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!communalInteractor.isCommunalEnabled) {
+            return
+        }
+        val tutorialIndicatorId = R.id.communal_tutorial_indicator
+        val width = resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_fixed_width)
+        val horizontalOffsetMargin =
+            resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_horizontal_offset)
+
+        constraintSet.apply {
+            constrainWidth(tutorialIndicatorId, width)
+            constrainHeight(tutorialIndicatorId, WRAP_CONTENT)
+            connect(
+                tutorialIndicatorId,
+                ConstraintSet.RIGHT,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.RIGHT,
+                horizontalOffsetMargin
+            )
+            connect(
+                tutorialIndicatorId,
+                ConstraintSet.TOP,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.TOP
+            )
+            connect(
+                tutorialIndicatorId,
+                ConstraintSet.BOTTOM,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.BOTTOM
+            )
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        communalTutorialIndicatorHandle?.dispose()
+        constraintLayout.removeView(R.id.communal_tutorial_indicator)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
new file mode 100644
index 0000000..ad02f62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
@@ -0,0 +1,64 @@
+package com.android.systemui.communal.ui.view.layout.sections
+
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.removeView
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** A keyguard section that hosts the communal hub. */
+class DefaultCommunalHubSection
+@Inject
+constructor(
+    private val viewModel: CommunalViewModel,
+) : KeyguardSection() {
+    private val communalHubViewId = R.id.communal_hub
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.addView(
+            ComposeFacade.createCommunalView(
+                    context = constraintLayout.context,
+                    viewModel = viewModel,
+                )
+                .apply { id = communalHubViewId },
+        )
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {}
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            connect(
+                communalHubViewId,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+            )
+            connect(
+                communalHubViewId,
+                ConstraintSet.TOP,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.TOP,
+            )
+            connect(
+                communalHubViewId,
+                ConstraintSet.END,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.END,
+            )
+            connect(
+                communalHubViewId,
+                ConstraintSet.BOTTOM,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.BOTTOM,
+            )
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.removeView(communalHubViewId)
+    }
+}
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
new file mode 100644
index 0000000..eaf9550
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.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.communal.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** View model for communal tutorial indicator on keyguard */
+class CommunalTutorialIndicatorViewModel
+@Inject
+constructor(
+    communalTutorialInteractor: CommunalTutorialInteractor,
+) {
+    /** An observable for whether the tutorial indicator view should be visible. */
+    val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable
+}
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
new file mode 100644
index 0000000..25c64ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.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.communal.ui.viewmodel
+
+import android.appwidget.AppWidgetHost
+import android.content.Context
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
+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.map
+
+@SysUISingleton
+class CommunalViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val appWidgetHost: AppWidgetHost,
+    communalInteractor: CommunalInteractor,
+    tutorialInteractor: CommunalTutorialInteractor,
+) {
+    /** Whether communal hub should show tutorial content. */
+    val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+
+    /** List of widgets to be displayed in the communal hub. */
+    val widgetContent: Flow<List<CommunalContentUiModel>> =
+        communalInteractor.widgetContent.map {
+            it.map {
+                // TODO(b/306406256): As adding and removing widgets functionalities are
+                // supported, cache the host views so they're not recreated each time.
+                val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
+                return@map CommunalContentUiModel(
+                    view = hostView,
+                    priority = it.priority,
+                    // All widgets have HALF size.
+                    size = CommunalContentSize.HALF,
+                )
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
index 8fba342..d7bbea6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
index 20b2494..f7b6b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
@@ -652,8 +652,7 @@
             CrossFadeHelper.fadeOut(
                     mLayout,
                     mFadeOutDuration,
-                    /* delay= */ 0,
-                    /* endRunnable= */ null);
+                    /* delay= */ 0);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 1a6f7e1..4bdea75 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,6 +22,7 @@
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 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
@@ -72,4 +73,13 @@
         windowInsets: StateFlow<WindowInsets?>,
         sceneByKey: Map<SceneKey, Scene>,
     ): View
+
+    /** Create a [View] to represent [viewModel] on screen. */
+    fun createCommunalView(
+        context: Context,
+        viewModel: CommunalViewModel,
+    ): View
+
+    /** Creates a container that hosts the communal UI and handles gesture transitions. */
+    fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
index 8e3b510..436b8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.SettingObserver
+import com.android.systemui.qs.UserSettingObserver
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -64,7 +64,8 @@
             .flatMapLatest { userInfo ->
                 conflatedCallbackFlow {
                         val observer =
-                            object : SettingObserver(secureSettings, null, setting, userInfo.id) {
+                            object :
+                                UserSettingObserver(secureSettings, null, setting, userInfo.id) {
                                 override fun handleValueChanged(
                                     value: Int,
                                     observedChange: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 04a9cae..d57f31f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -67,6 +67,7 @@
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.MediaRouter2Manager;
+import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjectionManager;
 import android.media.session.MediaSessionManager;
 import android.net.ConnectivityManager;
@@ -414,6 +415,13 @@
     }
 
     @Provides
+    @Singleton
+    static IMediaProjectionManager provideIMediaProjectionManager() {
+        return IMediaProjectionManager.Stub.asInterface(
+                ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
+    }
+
+    @Provides
     static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
         return MediaRouter2Manager.getInstance(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index ca725c0..5c38264 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -64,6 +64,7 @@
      * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated.
      */
     @Provides
+    @Deprecated
     public DisplayMetrics provideDisplayMetrics(Context context) {
         DisplayMetrics displayMetrics = new DisplayMetrics();
         context.getDisplay().getMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5d6949b..d8ff535 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -57,7 +57,6 @@
 import com.android.systemui.statusbar.gesture.GesturePointerEventListener
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
-import com.android.systemui.statusbar.phone.LockscreenWallpaper
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
 import com.android.systemui.stylus.StylusUsiPowerStartable
@@ -344,11 +343,6 @@
 
     @Binds
     @IntoMap
-    @ClassKey(LockscreenWallpaper::class)
-    abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
-
-    @Binds
-    @IntoMap
     @ClassKey(ScrimController::class)
     abstract fun bindScrimController(impl: ScrimController): CoreStartable
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4b6ad6d..04b2852d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -33,7 +33,6 @@
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.authentication.AuthenticationModule;
-import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.biometrics.FingerprintReEnrollNotification;
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
@@ -42,7 +41,8 @@
 import com.android.systemui.bouncer.ui.BouncerViewModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
-import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.common.ui.data.CommonUiDataLayerModule;
+import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -56,6 +56,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.keyboard.KeyboardModule;
+import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.log.dagger.MonitorLog;
@@ -169,7 +170,8 @@
         BouncerViewModule.class,
         ClipboardOverlayModule.class,
         ClockRegistryModule.class,
-        CommonRepositoryModule.class,
+        CommonUiDataLayerModule.class,
+        CommunalModule.class,
         ConnectivityModule.class,
         ControlsModule.class,
         CoroutinesModule.class,
@@ -181,6 +183,7 @@
         FalsingModule.class,
         FlagsModule.class,
         FooterActionsModule.class,
+        KeyEventRepositoryModule.class,
         KeyboardModule.class,
         KeyguardBlueprintModule.class,
         LetterboxModule.class,
@@ -298,9 +301,6 @@
     abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider();
 
     @BindsOptionalOf
-    abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider();
-
-    @BindsOptionalOf
     abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider();
 
     @BindsOptionalOf
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index e7f835f..c3aaef7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.deviceentry
 
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
 import dagger.Module
 
@@ -7,6 +8,7 @@
     includes =
         [
             DeviceEntryRepositoryModule::class,
+            DeviceEntryHapticsRepositoryModule::class,
         ],
 )
 object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
new file mode 100644
index 0000000..1458404
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Interface for classes that can access device-entry haptics application state. */
+interface DeviceEntryHapticsRepository {
+    /**
+     * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
+     */
+    val successHapticRequest: Flow<Boolean>
+
+    /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
+    val errorHapticRequest: Flow<Boolean>
+
+    fun requestSuccessHaptic()
+    fun handleSuccessHaptic()
+    fun requestErrorHaptic()
+    fun handleErrorHaptic()
+}
+
+/** Encapsulates application state for device entry haptics. */
+@SysUISingleton
+class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
+    private val _successHapticRequest = MutableStateFlow(false)
+    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
+
+    private val _errorHapticRequest = MutableStateFlow(false)
+    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
+
+    override fun requestSuccessHaptic() {
+        _successHapticRequest.value = true
+    }
+
+    override fun handleSuccessHaptic() {
+        _successHapticRequest.value = false
+    }
+
+    override fun requestErrorHaptic() {
+        _errorHapticRequest.value = true
+    }
+
+    override fun handleErrorHaptic() {
+        _errorHapticRequest.value = false
+    }
+}
+
+@Module
+interface DeviceEntryHapticsRepositoryModule {
+    @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
+}
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
new file mode 100644
index 0000000..53d6f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Business logic for device entry haptic events. Determines whether the haptic should play. In
+ * particular, there are extra guards for whether device entry error and successes hatpics should
+ * play when the physical fingerprint sensor is located on the power button.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryHapticsInteractor
+@Inject
+constructor(
+    private val repository: DeviceEntryHapticsRepository,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
+    biometricSettingsRepository: BiometricSettingsRepository,
+    keyEventInteractor: KeyEventInteractor,
+    powerInteractor: PowerInteractor,
+    private val systemClock: SystemClock,
+    private val logger: BiometricUnlockLogger,
+) {
+    private val powerButtonSideFpsEnrolled =
+        combineTransform(
+                fingerprintPropertyRepository.sensorType,
+                biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+            ) { sensorType, enrolledAndEnabled ->
+                if (sensorType == FingerprintSensorType.POWER_BUTTON) {
+                    emit(enrolledAndEnabled)
+                } else {
+                    emit(false)
+                }
+            }
+            .distinctUntilChanged()
+    private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown
+    private val lastPowerButtonWakeup: Flow<Long> =
+        powerInteractor.detailedWakefulness
+            .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) }
+            .map { systemClock.uptimeMillis() }
+            .onStart {
+                // If the power button hasn't been pressed, we still want this to evaluate to true:
+                // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs`
+                emit(recentPowerButtonPressThresholdMs * -1L - 1L)
+            }
+
+    val playSuccessHaptic: Flow<Boolean> =
+        repository.successHapticRequest
+            .filter { it }
+            .sample(
+                combine(
+                    powerButtonSideFpsEnrolled,
+                    powerButtonDown,
+                    lastPowerButtonWakeup,
+                    ::Triple
+                )
+            )
+            .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+                val sideFpsAllowsHaptic =
+                    !powerButtonDown &&
+                        systemClock.uptimeMillis() - lastPowerButtonWakeup >
+                            recentPowerButtonPressThresholdMs
+                val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
+                if (!allowHaptic) {
+                    logger.d("Skip success haptic. Recent power button press or button is down.")
+                    handleSuccessHaptic() // immediately handle, don't vibrate
+                }
+                allowHaptic
+            }
+    val playErrorHaptic: Flow<Boolean> =
+        repository.errorHapticRequest
+            .filter { it }
+            .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
+            .map { (sideFpsEnrolled, powerButtonDown) ->
+                val allowHaptic = !sideFpsEnrolled || !powerButtonDown
+                if (!allowHaptic) {
+                    logger.d("Skip error haptic. Power button is down.")
+                    handleErrorHaptic() // immediately handle, don't vibrate
+                }
+                allowHaptic
+            }
+
+    fun vibrateSuccess() {
+        repository.requestSuccessHaptic()
+    }
+
+    fun vibrateError() {
+        repository.requestErrorHaptic()
+    }
+
+    fun handleSuccessHaptic() {
+        repository.handleSuccessHaptic()
+    }
+
+    fun handleErrorHaptic() {
+        repository.handleErrorHaptic()
+    }
+
+    private val recentPowerButtonPressThresholdMs = 400L
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 4f16685..3e2ecee 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -47,6 +47,8 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
 /** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -54,10 +56,7 @@
     /** Display change event indicating a change to the given displayId has occurred. */
     val displayChangeEvent: Flow<Int>
 
-    /**
-     * Provides a nullable set of displays. Updates when new displays have been added or removed but
-     * not when a display's info has changed.
-     */
+    /** Provides the current set of displays. */
     val displays: Flow<Set<Display>>
 
     /**
@@ -112,10 +111,6 @@
                             trySend(DisplayEvent.Changed(displayId))
                         }
                     }
-                // Triggers an initial event when subscribed. This is needed to avoid getDisplays to
-                // be called when this class is constructed, but only when someone subscribes to
-                // this flow.
-                trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY))
                 displayManager.registerDisplayListener(
                     callback,
                     backgroundHandler,
@@ -125,6 +120,7 @@
                 )
                 awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
+            .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
             .flowOn(backgroundCoroutineDispatcher)
 
     override val displayChangeEvent: Flow<Int> =
@@ -134,13 +130,9 @@
         allDisplayEvents
             .map { getDisplays() }
             .flowOn(backgroundCoroutineDispatcher)
-            .stateIn(
-                applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                // To avoid getting displays on this object construction, they are get after the
-                // first event. allDisplayEvents emits a changed event when we subscribe to it.
-                initialValue = emptySet()
-            )
+            .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+
+    override val displays: Flow<Set<Display>> = enabledDisplays
 
     private fun getDisplays(): Set<Display> =
         traceSection("DisplayRepository#getDisplays()") {
@@ -148,8 +140,6 @@
         }
 
     /** Propagate to the listeners only enabled displays */
-    override val displays: Flow<Set<Display>> = enabledDisplays
-
     private val enabledDisplayIds: Flow<Set<Int>> =
         enabledDisplays
             .map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() }
@@ -251,6 +241,7 @@
                 val id = pendingDisplayIds.maxOrNull() ?: return@map null
                 object : DisplayRepository.PendingDisplay {
                     override val id = id
+
                     override suspend fun enable() {
                         traceSection("DisplayRepository#enable($id)") {
                             if (DEBUG) {
@@ -303,8 +294,12 @@
 private interface DisplayConnectionListener : DisplayListener {
 
     override fun onDisplayConnected(id: Int) {}
+
     override fun onDisplayDisconnected(id: Int) {}
+
     override fun onDisplayAdded(id: Int) {}
+
     override fun onDisplayRemoved(id: Int) {}
+
     override fun onDisplayChanged(id: Int) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 7510cf6c..87b0f01 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -15,14 +15,16 @@
  */
 package com.android.systemui.display.ui.view
 
-import android.app.Dialog
 import android.content.Context
 import android.os.Bundle
-import android.view.Gravity
 import android.view.View
-import android.view.WindowManager
 import android.widget.TextView
+import androidx.core.view.updatePadding
+import com.android.systemui.biometrics.Utils
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
+import com.android.systemui.statusbar.policy.ConfigurationController
+import kotlin.math.max
 
 /**
  * Dialog used to decide what to do with a connected display.
@@ -34,8 +36,9 @@
     context: Context,
     private val onStartMirroringClickListener: View.OnClickListener,
     private val onCancelMirroring: View.OnClickListener,
+    configurationController: ConfigurationController? = null,
     theme: Int = R.style.Theme_SystemUI_Dialog,
-) : Dialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, configurationController, theme) {
 
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
@@ -43,13 +46,8 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        window?.apply {
-            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-            setGravity(Gravity.BOTTOM)
-        }
         setContentView(R.layout.connected_display_dialog)
-        setCanceledOnTouchOutside(true)
+
         mirrorButton =
             requireViewById<TextView>(R.id.enable_display).apply {
                 setOnClickListener(onStartMirroringClickListener)
@@ -63,5 +61,23 @@
                 onCancelMirroring.onClick(null)
             }
         }
+        setupInsets()
+    }
+
+    private fun setupInsets() {
+        // This avoids overlap between dialog content and navigation bars.
+        requireViewById<View>(R.id.cd_bottom_sheet).apply {
+            val navbarInsets = Utils.getNavbarInsets(context)
+            val defaultDialogBottomInset =
+                context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
+            // we only care about the bottom inset as in all other configuration where navigations
+            // are in other display sides there is no overlap with the dialog.
+            updatePadding(bottom = max(navbarInsets.bottom, defaultDialogBottomInset))
+        }
+    }
+
+    override fun onConfigurationChanged() {
+        super.onConfigurationChanged()
+        setupInsets()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 86ef439..91f535d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import com.android.systemui.statusbar.policy.ConfigurationController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -41,7 +42,8 @@
     private val context: Context,
     private val connectedDisplayInteractor: ConnectedDisplayInteractor,
     @Application private val scope: CoroutineScope,
-    @Background private val bgDispatcher: CoroutineDispatcher
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val configurationController: ConfigurationController
 ) {
 
     private var dialog: Dialog? = null
@@ -71,7 +73,8 @@
                     onCancelMirroring = {
                         scope.launch(bgDispatcher) { pendingDisplay.ignore() }
                         hideDialog()
-                    }
+                    },
+                    configurationController
                 )
                 .apply { show() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 5eb9808..9c13a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -18,6 +18,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import javax.inject.Inject;
 
@@ -28,16 +29,19 @@
 public class DozeAuthRemover implements DozeMachine.Part {
 
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     @Inject
-    public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor) {
+    public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SelectedUserInteractor selectedUserInteractor) {
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
-            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
                 mKeyguardUpdateMonitor.clearBiometricRecognized();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 7da2cf1..ba57918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -31,13 +31,13 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.wakelock.SettableWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -81,6 +81,7 @@
     @Nullable private UdfpsController mUdfpsController;
     private final DozeLog mDozeLog;
     private final DozeScreenBrightness mDozeScreenBrightness;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private int mPendingScreenState = Display.STATE_UNKNOWN;
     private SettableWakeLock mWakeLock;
@@ -95,7 +96,8 @@
             AuthController authController,
             Provider<UdfpsController> udfpsControllerProvider,
             DozeLog dozeLog,
-            DozeScreenBrightness dozeScreenBrightness) {
+            DozeScreenBrightness dozeScreenBrightness,
+            SelectedUserInteractor selectedUserInteractor) {
         mDozeService = service;
         mHandler = handler;
         mParameters = parameters;
@@ -105,6 +107,7 @@
         mUdfpsControllerProvider = udfpsControllerProvider;
         mDozeLog = dozeLog;
         mDozeScreenBrightness = dozeScreenBrightness;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         updateUdfpsController();
         if (mUdfpsController == null) {
@@ -113,7 +116,7 @@
     }
 
     private void updateUdfpsController() {
-        if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+        if (mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId())) {
             mUdfpsController = mUdfpsControllerProvider.get();
         } else {
             mUdfpsController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 07efbfe..3194942 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,12 +48,11 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -101,7 +100,7 @@
     private final SecureSettings mSecureSettings;
     private final DevicePostureController mDevicePostureController;
     private final AuthController mAuthController;
-    private final UserTracker mUserTracker;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final boolean mScreenOffUdfpsEnabled;
 
     // Sensors
@@ -158,7 +157,7 @@
             SecureSettings secureSettings,
             AuthController authController,
             DevicePostureController devicePostureController,
-            UserTracker userTracker
+            SelectedUserInteractor selectedUserInteractor
     ) {
         mSensorManager = sensorManager;
         mConfig = config;
@@ -171,15 +170,15 @@
         mProximitySensor.setTag(TAG);
         mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
         mListeningProxSensors = !mSelectivelyRegisterProxSensors;
+        mSelectedUserInteractor = selectedUserInteractor;
         mScreenOffUdfpsEnabled =
-                config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
+                config.screenOffUdfpsEnabled(mSelectedUserInteractor.getSelectedUserId());
         mDevicePostureController = devicePostureController;
         mDevicePosture = mDevicePostureController.getDevicePosture();
         mAuthController = authController;
-        mUserTracker = userTracker;
 
         mUdfpsEnrolled =
-                mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
+                mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
         mAuthController.addCallback(mAuthControllerCallback);
         mTriggerSensors = new TriggerSensor[] {
                 new TriggerSensor(
@@ -255,7 +254,8 @@
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
                         Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                         mConfig.wakeScreenGestureAvailable()
-                          && mConfig.alwaysOnEnabled(mUserTracker.getUserId()),
+                          && mConfig.alwaysOnEnabled(
+                                  mSelectedUserInteractor.getSelectedUserId(true)),
                         DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         false /* reports touch coordinates */,
                         false /* touchscreen */
@@ -296,12 +296,13 @@
 
     private boolean udfpsLongPressConfigured() {
         return mUdfpsEnrolled
-                && (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) || mScreenOffUdfpsEnabled);
+                && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true))
+                || mScreenOffUdfpsEnabled);
     }
 
     private boolean quickPickUpConfigured() {
         return mUdfpsEnrolled
-                && mConfig.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser());
+                && mConfig.quickPickupSensorEnabled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     /**
@@ -471,7 +472,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
-            if (userId != mUserTracker.getUserId()) {
+            if (userId != mSelectedUserInteractor.getSelectedUserId(true)) {
                 return;
             }
             for (TriggerSensor s : mTriggerSensors) {
@@ -697,13 +698,13 @@
         }
 
         protected boolean enabledBySetting() {
-            if (!mConfig.enabled(mUserTracker.getUserId())) {
+            if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) {
                 return false;
             } else if (TextUtils.isEmpty(mSetting)) {
                 return true;
             }
             return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0,
-                    mUserTracker.getUserId()) != 0;
+                    mSelectedUserInteractor.getSelectedUserId(true)) != 0;
         }
 
         @Override
@@ -873,7 +874,7 @@
 
         private void updateUdfpsEnrolled() {
             mUdfpsEnrolled = mAuthController.isUdfpsEnrolled(
-                    KeyguardUpdateMonitor.getCurrentUser());
+                    mSelectedUserInteractor.getSelectedUserId());
             for (TriggerSensor sensor : mTriggerSensors) {
                 if (REASON_SENSOR_QUICK_PICKUP == sensor.mPulseReason) {
                     sensor.setConfigured(quickPickUpConfigured());
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 85272a6..795c3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximityCheck;
@@ -102,6 +103,7 @@
     private final AuthController mAuthController;
     private final KeyguardStateController mKeyguardStateController;
     private final UserTracker mUserTracker;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final UiEventLogger mUiEventLogger;
 
     private long mNotificationPulseTime;
@@ -201,7 +203,8 @@
             SessionTracker sessionTracker,
             KeyguardStateController keyguardStateController,
             DevicePostureController devicePostureController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mDozeHost = dozeHost;
         mConfig = config;
@@ -213,7 +216,7 @@
 
         mDozeSensors = new DozeSensors(mContext.getResources(), mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
-                secureSettings, authController, devicePostureController, userTracker);
+                secureSettings, authController, devicePostureController, selectedUserInteractor);
         mDockManager = dockManager;
         mProxCheck = proxCheck;
         mDozeLog = dozeLog;
@@ -222,6 +225,7 @@
         mUiEventLogger = uiEventLogger;
         mKeyguardStateController = keyguardStateController;
         mUserTracker = userTracker;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     @Override
@@ -246,7 +250,7 @@
             return;
         }
         mNotificationPulseTime = SystemClock.elapsedRealtime();
-        if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
+        if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) {
             runIfNotNull(onPulseSuppressedListener);
             mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
index 83c239f..dd58604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -17,15 +17,19 @@
 package com.android.systemui.flags
 
 import android.util.Log
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.ConditionalRestarter.Condition
+import com.android.systemui.util.kotlin.UnflaggedApplication
+import com.android.systemui.util.kotlin.UnflaggedBackground
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 import javax.inject.Named
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 
 /** Restarts the process after all passed in [Condition]s are true. */
@@ -35,11 +39,10 @@
     private val systemExitRestarter: SystemExitRestarter,
     private val conditions: Set<@JvmSuppressWildcards Condition>,
     @Named(RESTART_DELAY) private val restartDelaySec: Long,
-    @Application private val applicationScope: CoroutineScope,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @UnflaggedApplication private val applicationScope: CoroutineScope,
+    @UnflaggedBackground private val backgroundDispatcher: CoroutineContext,
 ) : Restarter {
 
-    private var restartJob: Job? = null
     private var pendingReason = ""
     private var androidRestartRequested = false
 
@@ -57,17 +60,19 @@
     private fun scheduleRestart(reason: String = "") {
         pendingReason = if (reason.isEmpty()) pendingReason else reason
 
-        if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
-            if (restartJob == null) {
-                restartJob =
-                    applicationScope.launch(backgroundDispatcher) {
+        applicationScope.launch(backgroundDispatcher) {
+            combine(conditions.map { condition -> condition.canRestartNow }) { it.all { it } }
+                // Once all conditions are met, delay.
+                .transformLatest { allConditionsMet ->
+                    if (allConditionsMet) {
                         delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
-                        restartNow()
+                        emit(Unit)
                     }
-            }
-        } else {
-            restartJob?.cancel()
-            restartJob = null
+                }
+                // Once we have successfully delayed _once_, continue to restart.
+                .first()
+
+            restartNow()
         }
     }
 
@@ -94,7 +99,7 @@
          * multiple [Condition]s are being checked. If any one [Condition] returns false, all the
          * [Condition]s will need to be rechecked on the next restart attempt.
          */
-        fun canRestartNow(retryFn: () -> Unit): Boolean
+        val canRestartNow: Flow<Boolean>
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 6bbd40c..1c2ff4b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -31,12 +31,12 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.systemui.FeatureFlags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -56,15 +56,15 @@
 
 /**
  * Concrete implementation of the a Flag manager that returns default values for debug builds
- *
+ * <p>
  * Flags can be set (or unset) via the following adb command:
- *
+ * <p>
  * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
- *
+ * <p>
  * Alternatively, you can change flags via a broadcast intent:
- *
+ * <p>
  * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
- *
+ * <p>
  * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
  */
 @SysUISingleton
@@ -81,6 +81,7 @@
     private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>();
     private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
     private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
+    private final FeatureFlags mGantryFlags;
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -125,6 +126,7 @@
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
+            FeatureFlags gantryFlags,
             Restarter restarter) {
         mFlagManager = flagManager;
         mContext = context;
@@ -133,6 +135,7 @@
         mSystemProperties = systemProperties;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
+        mGantryFlags = gantryFlags;
         mRestarter = restarter;
     }
 
@@ -260,9 +263,8 @@
         if (!hasServerOverride
                 && !defaultValue
                 && result == null
-                && !flag.getName().equals(Flags.TEAMFOOD.getName())
                 && flag.getTeamfood()) {
-            return isEnabled(Flags.TEAMFOOD);
+            return mGantryFlags.sysuiTeamfood();
         }
 
         return result == null ? mServerFlagReader.readServerOverride(
@@ -319,8 +321,7 @@
             Log.w(TAG, "Failed to set flag " + name + " to " + value);
             return;
         }
-        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
-                UserHandle.USER_CURRENT);
+        mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), data);
     }
 
     <T> void eraseFlag(Flag<T> flag) {
@@ -354,8 +355,7 @@
     /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
     private void eraseInternal(String name) {
         // We can't actually "erase" things from settings, but we can set them to empty!
-        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
-                UserHandle.USER_CURRENT);
+        mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), "");
         Log.i(TAG, "Erase name " + name);
     }
 
@@ -537,7 +537,7 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: true");
-
+        pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
         pw.println("booleans: " + mBooleanFlagCache.size());
         // Sort our flags for dumping
         TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f6f24e0..ff1df36 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -36,7 +36,10 @@
  * See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb.
  */
 object Flags {
-    @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
+    // IGNORE ME!
+    // Because flags are static, we need an ever-present flag to reference in some of the internal
+    // code that ensure that other flags are referenced and available.
+    @JvmField val NULL_FLAG = unreleasedFlag("null_flag")
 
     // 100 - notification
     // TODO(b/297792660): Tracking Bug
@@ -84,11 +87,6 @@
     @JvmField
     val NOTIFICATION_SHELF_REFACTOR = releasedFlag("notification_shelf_refactor")
 
-    // TODO(b/290787599): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ICON_CONTAINER_REFACTOR =
-        unreleasedFlag("notification_icon_container_refactor")
-
     // TODO(b/288326013): Tracking Bug
     @JvmField
     val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
@@ -115,12 +113,12 @@
     // TODO(b/292213543): Tracking Bug
     @JvmField
     val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            unreleasedFlag("notification_group_expansion_change", teamfood = true)
+            releasedFlag("notification_group_expansion_change")
 
     // TODO(b/301955929)
     @JvmField
     val NOTIF_LS_BACKGROUND_THREAD =
-            unreleasedFlag("notification_lockscreen_mgr_bg_thread")
+            unreleasedFlag("notification_lockscreen_mgr_bg_thread", teamfood = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -177,6 +175,10 @@
     @JvmField
     val NEW_AOD_TRANSITION = unreleasedFlag("new_aod_transition", teamfood = true)
 
+    // TODO(b/305984787):
+    @JvmField
+    val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true)
+
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
     @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor")
@@ -401,6 +403,9 @@
     @JvmField val SIGNAL_CALLBACK_DEPRECATION =
         unreleasedFlag("signal_callback_deprecation", teamfood = true)
 
+    // TODO(b/301610137): Tracking bug
+    @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true)
+
     // TODO(b/265892345): Tracking Bug
     val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
 
@@ -413,7 +418,7 @@
         releasedFlag("incompatible_charging_battery_icon")
 
     // TODO(b/293585143): Tracking Bug
-    val INSTANT_TETHER = unreleasedFlag("instant_tether")
+    val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
 
     // TODO(b/294588085): Tracking Bug
     val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
@@ -473,6 +478,9 @@
     // TODO(b/270437894): Tracking Bug
     val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume")
 
+    // TODO(b/304506662): Tracking Bug
+    val MEDIA_DEVICE_NAME_FIX = releasedFlag("media_device_name_fix")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
 
@@ -494,10 +502,9 @@
     @Keep
     @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING =
-        unreleasedFlag(
-            name = "record_task_content",
+        releasedFlag(
+            name = "enable_record_task_content",
             namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-            teamfood = true
         )
 
     // TODO(b/254512674): Tracking Bug
@@ -537,12 +544,6 @@
     val LOCKSCREEN_ENABLE_LANDSCAPE =
             unreleasedFlag("lockscreen.enable_landscape")
 
-    // TODO(b/273443374): Tracking Bug
-    @Keep
-    @JvmField
-    val LOCKSCREEN_LIVE_WALLPAPER =
-        sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true)
-
     // TODO(b/281648899): Tracking bug
     @Keep
     @JvmField
@@ -617,7 +618,7 @@
 
     /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
     @JvmField
-    val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+    val MULTI_DISPLAY_SCREENSHOT = releasedFlag("multi_display_screenshot")
 
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
@@ -667,8 +668,6 @@
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
-    // TODO(b/259264861): Tracking Bug
-    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used")
@@ -723,7 +722,7 @@
     @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag("keyboard_backlight_indicator")
 
     // TODO(b/277192623): Tracking Bug
-    @JvmField val KEYBOARD_EDUCATION = unreleasedFlag("keyboard_education", teamfood = true)
+    @JvmField val KEYBOARD_EDUCATION = releasedFlag("keyboard_education")
 
     // TODO(b/277201412): Tracking Bug
     @JvmField
@@ -763,14 +762,14 @@
 
     // TODO(b/283740863): Tracking Bug
     @JvmField
-    val ENABLE_NEW_PRIVACY_DIALOG =
-        unreleasedFlag("enable_new_privacy_dialog", teamfood = true)
+    val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
 
     // TODO(b/289573946): Tracking Bug
-    @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true)
+    @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")
+    @JvmField val CALL_LAYOUT_ASYNC_SET_DATA =
+            unreleasedFlag("call_layout_async_set_data", teamfood = true)
 
     // TODO(b/302144438): Tracking Bug
     @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
@@ -786,13 +785,11 @@
 
     // TODO(b/290213663): Tracking Bug
     @JvmField
-    val ONE_WAY_HAPTICS_API_MIGRATION =
-            unreleasedFlag("oneway_haptics_api_migration", teamfood = true)
+    val ONE_WAY_HAPTICS_API_MIGRATION = releasedFlag("oneway_haptics_api_migration")
 
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
-    val ENABLE_CLOCK_KEYGUARD_PRESENTATION =
-        unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true)
+    val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
 
     /** Enable the Compose implementation of the PeopleSpaceActivity. */
     @JvmField
@@ -812,8 +809,7 @@
 
     // TODO(b/287205379): Tracking bug
     @JvmField
-    val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer",
-            teamfood = true)
+    val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
 
     /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
new file mode 100644
index 0000000..f5b30cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.flags
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class NotOccludedCondition
+@Inject
+constructor(
+    private val keyguardTransitionInteractorLazy: Lazy<KeyguardTransitionInteractor>,
+) : ConditionalRestarter.Condition {
+
+    override val canRestartNow: Flow<Boolean>
+        get() {
+            return keyguardTransitionInteractorLazy
+                .get()
+                .transitionValue(KeyguardState.OCCLUDED)
+                .map { it == 0f }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
index 3120638..dc08570 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -16,34 +16,34 @@
 
 package com.android.systemui.flags
 
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.statusbar.policy.BatteryController
+import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
 
 /** Returns true when the device is plugged in. */
 class PluggedInCondition
 @Inject
 constructor(
-    private val batteryController: BatteryController,
+    private val batteryControllerLazy: Lazy<BatteryController>,
 ) : ConditionalRestarter.Condition {
 
-    var listenersAdded = false
-    var retryFn: (() -> Unit)? = null
-
-    val batteryCallback =
-        object : BatteryController.BatteryStateChangeCallback {
-            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-                retryFn?.invoke()
+    override val canRestartNow = conflatedCallbackFlow {
+        val batteryCallback =
+            object : BatteryController.BatteryStateChangeCallback {
+                override fun onBatteryLevelChanged(
+                    level: Int,
+                    pluggedIn: Boolean,
+                    charging: Boolean
+                ) {
+                    trySend(pluggedIn)
+                }
             }
-        }
+        batteryControllerLazy.get().addCallback(batteryCallback)
 
-    override fun canRestartNow(retryFn: () -> Unit): Boolean {
-        if (!listenersAdded) {
-            listenersAdded = true
-            batteryController.addCallback(batteryCallback)
-        }
+        trySend(batteryControllerLazy.get().isPluggedIn)
 
-        this.retryFn = retryFn
-
-        return batteryController.isPluggedIn
+        awaitClose { batteryControllerLazy.get().removeCallback(batteryCallback) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
new file mode 100644
index 0000000..4a5cc64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.Dependency
+
+/**
+ * This class promotes best practices for flag guarding System UI view refactors.
+ * * [isEnabled] allows changing an implementation.
+ * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and
+ *   ensure that it is not being invoked accidentally in the post-flag refactor.
+ * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when
+ *   invoked on flag-disabled builds, but with a check that should crash eng builds or tests when
+ *   the expectation is violated.
+ *
+ * The constructors require that you provide a [FeatureFlags] instance. If you're using this in a
+ * View class, it's acceptable to ue the [forView] constructor methods, which do not require one,
+ * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
+ * inside Views where injecting flag values after initialization can be error-prone.
+ */
+class RefactorFlag
+private constructor(
+    private val injectedFlags: FeatureFlags?,
+    private val flagName: Any,
+    private val readFlagValue: (FeatureFlags) -> Boolean
+) {
+    constructor(
+        flags: FeatureFlags,
+        flag: UnreleasedFlag
+    ) : this(flags, flag, { it.isEnabled(flag) })
+
+    constructor(flags: FeatureFlags, flag: ReleasedFlag) : this(flags, flag, { it.isEnabled(flag) })
+
+    /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
+    val isEnabled by lazy {
+        @Suppress("DEPRECATION")
+        val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
+        readFlagValue(featureFlags)
+    }
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setController(NotificationShelfController notificationShelfController) {
+     *     mShelfRefactor.assertInLegacyMode();
+     *     mController = notificationShelfController;
+     * }
+     * ````
+     */
+    fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setShelfIcons(NotificationIconContainer icons) {
+     *     if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+     *     mShelfIcons = icons;
+     * }
+     * ```
+     */
+    fun isUnexpectedlyInLegacyMode(): Boolean =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
+
+    companion object {
+        private const val TAG = "RefactorFlag"
+
+        /** Construct a [RefactorFlag] within View construction where injection is impossible. */
+        @JvmStatic
+        @JvmOverloads
+        fun forView(flag: UnreleasedFlag, flags: FeatureFlags? = null) =
+            RefactorFlag(flags, flag) { it.isEnabled(flag) }
+
+        /** Construct a [RefactorFlag] within View construction where injection is impossible. */
+        @JvmStatic
+        @JvmOverloads
+        fun forView(flag: ReleasedFlag, flags: FeatureFlags? = null) =
+            RefactorFlag(flags, flag) { it.isEnabled(flag) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
new file mode 100644
index 0000000..2aa397f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+
+/**
+ * Utilities for writing your own objects to uphold refactor flag conventions.
+ *
+ * Example usage:
+ * ```
+ * object SomeRefactor {
+ *     const val FLAG_NAME = Flags.SOME_REFACTOR
+ *     @JvmStatic inline val isEnabled get() = Flags.someRefactor()
+ *     @JvmStatic inline fun isUnexpectedlyInLegacyMode() =
+ *         RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ *     @JvmStatic inline fun assertInLegacyMode() =
+ *         RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ * }
+ * ```
+ */
+@Suppress("NOTHING_TO_INLINE")
+object RefactorFlagUtils {
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setNewController(SomeController someController) {
+     *     if (SomeRefactor.isUnexpectedlyInLegacyMode()) return;
+     *     mSomeController = someController;
+     * }
+     * ```
+     */
+    inline fun isUnexpectedlyInLegacyMode(isEnabled: Boolean, flagName: Any): Boolean {
+        val inLegacyMode = !isEnabled
+        if (inLegacyMode) {
+            val message = "New code path expects $flagName to be enabled."
+            Log.wtf("RefactorFlag", message, IllegalStateException(message))
+        }
+        return inLegacyMode
+    }
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setSomeLegacyController(SomeController someController) {
+     *     SomeRefactor.assertInLegacyMode();
+     *     mSomeController = someController;
+     * }
+     * ````
+     */
+    inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) =
+        check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
index 49e61af..3c9bc36 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -16,34 +16,19 @@
 
 package com.android.systemui.flags
 
-import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 
 /** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
 class ScreenIdleCondition
 @Inject
-constructor(
-    private val wakefulnessLifecycle: WakefulnessLifecycle,
-) : ConditionalRestarter.Condition {
+constructor(private val powerInteractorLazy: Lazy<PowerInteractor>) :
+    ConditionalRestarter.Condition {
 
-    var listenersAdded = false
-    var retryFn: (() -> Unit)? = null
-
-    val observer =
-        object : WakefulnessLifecycle.Observer {
-            override fun onFinishedGoingToSleep() {
-                retryFn?.invoke()
-            }
+    override val canRestartNow: Flow<Boolean>
+        get() {
+            return powerInteractorLazy.get().isAsleep
         }
-
-    override fun canRestartNow(retryFn: () -> Unit): Boolean {
-        if (!listenersAdded) {
-            listenersAdded = true
-            wakefulnessLifecycle.addObserver(observer)
-        }
-
-        this.retryFn = retryFn
-
-        return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
deleted file mode 100644
index eaecda5..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.Dependency
-
-/**
- * This class promotes best practices for flag guarding System UI view refactors.
- * * [isEnabled] allows changing an implementation.
- * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
- *   ensure that it is not being invoked accidentally in the post-flag refactor.
- * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
- *   flag-disabled builds, but with a check that should crash eng builds or tests when the
- *   expectation is violated.
- *
- * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it,
- * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
- * inside views where injecting flag values after initialization can be error-prone.
- */
-class ViewRefactorFlag
-private constructor(
-    private val injectedFlags: FeatureFlags?,
-    private val flag: BooleanFlag,
-    private val readFlagValue: (FeatureFlags) -> Boolean
-) {
-    @JvmOverloads
-    constructor(
-        flags: FeatureFlags? = null,
-        flag: UnreleasedFlag
-    ) : this(flags, flag, { it.isEnabled(flag) })
-
-    @JvmOverloads
-    constructor(
-        flags: FeatureFlags? = null,
-        flag: ReleasedFlag
-    ) : this(flags, flag, { it.isEnabled(flag) })
-
-    /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
-    val isEnabled by lazy {
-        @Suppress("DEPRECATION")
-        val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
-        readFlagValue(featureFlags)
-    }
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is enabled to ensure that the refactor author catches issues in testing.
-     *
-     * Example usage:
-     * ```
-     * public void setController(NotificationShelfController notificationShelfController) {
-     *     mShelfRefactor.assertDisabled();
-     *     mController = notificationShelfController;
-     * }
-     * ````
-     */
-    fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." }
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     *
-     * Example usage:
-     * ```
-     * public void setShelfIcons(NotificationIconContainer icons) {
-     *     if (mShelfRefactor.expectEnabled()) {
-     *         mShelfIcons = icons;
-     *     }
-     * }
-     * ```
-     */
-    fun expectEnabled(): Boolean {
-        if (!isEnabled) {
-            val message = "Code path not supported when $flag is disabled."
-            Log.wtf(TAG, message, Exception(message))
-        }
-        return isEnabled
-    }
-
-    private companion object {
-        private const val TAG = "ViewRefactorFlag"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ac40230..5cc2e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -134,6 +134,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -195,6 +196,7 @@
     private final IDreamManager mDreamManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final LockPatternUtils mLockPatternUtils;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final TelephonyListenerManager mTelephonyListenerManager;
     private final KeyguardStateController mKeyguardStateController;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -364,7 +366,8 @@
             PackageManager packageManager,
             ShadeController shadeController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DialogLaunchAnimator dialogLaunchAnimator) {
+            DialogLaunchAnimator dialogLaunchAnimator,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -399,6 +402,7 @@
         mShadeController = shadeController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -713,7 +717,8 @@
                 mUiEventLogger,
                 mShadeController,
                 mKeyguardUpdateMonitor,
-                mLockPatternUtils);
+                mLockPatternUtils,
+                mSelectedUserInteractor);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -749,7 +754,7 @@
     @VisibleForTesting
     boolean shouldDisplayBugReport(@Nullable UserInfo user) {
         return user != null && user.isAdmin()
-                && mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
+                && mSecureSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
                 user.id) != 0;
     }
 
@@ -1091,7 +1096,7 @@
 
         @Override
         public boolean showBeforeProvisioning() {
-            return Build.isDebuggable() && mGlobalSettings.getIntForUser(
+            return Build.isDebuggable() && mSecureSettings.getIntForUser(
                     Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                     && getCurrentUser().isAdmin();
         }
@@ -2222,6 +2227,7 @@
         private GestureDetector mGestureDetector;
         private final ShadeController mShadeController;
         private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private SelectedUserInteractor mSelectedUserInteractor;
         private LockPatternUtils mLockPatternUtils;
         private float mWindowDimAmount;
 
@@ -2300,7 +2306,8 @@
                 UiEventLogger uiEventLogger,
                 ShadeController shadeController,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
-                LockPatternUtils lockPatternUtils) {
+                LockPatternUtils lockPatternUtils,
+                SelectedUserInteractor selectedUserInteractor) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
             // dismiss this dialog when the device is locked.
             super(context, themeRes, false /* dismissOnDeviceLock */);
@@ -2321,6 +2328,7 @@
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mGestureDetector = new GestureDetector(mContext, mGestureListener);
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         @Override
@@ -2453,10 +2461,10 @@
             }
 
             // If user entered from the lock screen and smart lock was enabled, disable it
-            int user = KeyguardUpdateMonitor.getCurrentUser();
+            int user = mSelectedUserInteractor.getSelectedUserId();
             boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
             if (mKeyguardShowing && userHasTrust) {
-                mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+                mLockPatternUtils.requireCredentialEntry(user);
                 showSmartLockDisabledMessage();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 20d99d1..7b33e11 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -32,6 +32,8 @@
     @FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f,
     /** Additional time delta to wait between drag texture vibrations */
     @FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f,
+    /** Progress threshold beyond which a new drag texture is delivered */
+    @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f,
     /** Number of low ticks in a drag texture composition. This is not expected to change */
     val numberOfLowTicks: Int = 5,
     /** Maximum velocity allowed for vibration scaling. This is not expected to change. */
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index e6de156..f313fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -46,6 +46,8 @@
     private val positionAccelerateInterpolator =
         AccelerateInterpolator(config.progressInterpolatorFactor)
     private var dragTextureLastTime = clock.elapsedRealtime()
+    var dragTextureLastProgress = -1f
+        private set
     private val lowTickDurationMs =
         vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0]
     private var hasVibratedAtLowerBookend = false
@@ -91,6 +93,9 @@
         val elapsedSinceLastDrag = currentTime - dragTextureLastTime
         if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return
 
+        val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
+        if (deltaProgress < config.deltaProgressForDragThreshold) return
+
         val velocityInterpolated =
             velocityAccelerateInterpolator.getInterpolation(
                 min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -116,11 +121,14 @@
         }
         vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
         dragTextureLastTime = currentTime
+        dragTextureLastProgress = normalizedSliderProgress
     }
 
     override fun onHandleAcquiredByTouch() {}
 
-    override fun onHandleReleasedFromTouch() {}
+    override fun onHandleReleasedFromTouch() {
+        dragTextureLastProgress = -1f
+    }
 
     override fun onLowerBookend() {
         if (!hasVibratedAtLowerBookend) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
new file mode 100644
index 0000000..5bc5d0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.data.repository
+
+import android.view.KeyEvent
+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.statusbar.CommandQueue
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for classes that encapsulate application state for key event presses. */
+interface KeyEventRepository {
+    /** Observable for whether the power button key is pressed/down or not. */
+    val isPowerButtonDown: Flow<Boolean>
+}
+
+@SysUISingleton
+class KeyEventRepositoryImpl
+@Inject
+constructor(
+    val commandQueue: CommandQueue,
+) : KeyEventRepository {
+    override val isPowerButtonDown: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : CommandQueue.Callbacks {
+                override fun handleSystemKey(event: KeyEvent) {
+                    if (event.keyCode == KeyEvent.KEYCODE_POWER) {
+                        trySendWithFailureLogging(event.isDown, TAG, "updated isPowerButtonDown")
+                    }
+                }
+            }
+        commandQueue.addCallback(callback)
+        awaitClose { commandQueue.removeCallback(callback) }
+    }
+
+    companion object {
+        private const val TAG = "KeyEventRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
rename to packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt
index 9b0c3fa..afba5db 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.ui.data.repository
+package com.android.systemui.keyevent.data.repository
 
 import dagger.Binds
 import dagger.Module
 
 @Module
-interface CommonRepositoryModule {
-    @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+interface KeyEventRepositoryModule {
+    @Binds fun keyEventRepository(impl: KeyEventRepositoryImpl): KeyEventRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
index 3f2f67d..9949fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -13,55 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui.keyevent.domain.interactor
 
-import android.view.KeyEvent
-import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.keyevent.data.repository.KeyEventRepository
 import javax.inject.Inject
 
 /**
- * Sends key events to the appropriate interactors and then acts upon key events that haven't
- * already been handled but should be handled by SystemUI.
+ * Business logic for all key event state. This includes all key events, regardless of whether
+ * they've been handled or not by a consumer.
+ *
+ * For key events that SysUI wants to properly handle, see [SysUIKeyEventHandler].
  */
 @SysUISingleton
 class KeyEventInteractor
 @Inject
 constructor(
-    private val backActionInteractor: BackActionInteractor,
-    private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+    repository: KeyEventRepository,
 ) {
-    fun dispatchKeyEvent(event: KeyEvent): Boolean {
-        if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
-            return true
-        }
-
-        when (event.keyCode) {
-            KeyEvent.KEYCODE_BACK -> {
-                if (event.handleAction()) {
-                    backActionInteractor.onBackRequested()
-                }
-                return true
-            }
-        }
-        return false
-    }
-
-    fun interceptMediaKey(event: KeyEvent): Boolean {
-        return keyguardKeyEventInteractor.interceptMediaKey(event)
-    }
-
-    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
-        return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
-    }
-
-    companion object {
-        // Most actions shouldn't be handled on the down event and instead handled on subsequent
-        // key events like ACTION_UP.
-        fun KeyEvent.handleAction(): Boolean {
-            return action != KeyEvent.ACTION_DOWN
-        }
-    }
+    val isPowerButtonDown = repository.isPowerButtonDown
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
new file mode 100644
index 0000000..1febc79
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import javax.inject.Inject
+
+/**
+ * Sends key events to the appropriate interactors and then acts upon key events that haven't
+ * already been handled but should be handled by SystemUI.
+ *
+ * To observe any key event states, see [KeyEventInteractor].
+ */
+@SysUISingleton
+class SysUIKeyEventHandler
+@Inject
+constructor(
+    private val backActionInteractor: BackActionInteractor,
+    private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+) {
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
+            return true
+        }
+
+        when (event.keyCode) {
+            KeyEvent.KEYCODE_BACK -> {
+                if (event.handleAction()) {
+                    backActionInteractor.onBackRequested()
+                }
+                return true
+            }
+        }
+        return false
+    }
+
+    fun interceptMediaKey(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.interceptMediaKey(event)
+    }
+
+    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
+    }
+
+    companion object {
+        // Most actions shouldn't be handled on the down event and instead handled on subsequent
+        // key events like ACTION_UP.
+        fun KeyEvent.handleAction(): Boolean {
+            return action != KeyEvent.ACTION_DOWN
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index bc07139..6f491d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -36,9 +36,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.util.TraceUtils.Companion.runBlocking
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
 
 class CustomizationProvider :
     ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@@ -132,7 +132,7 @@
             throw UnsupportedOperationException()
         }
 
-        return runBlocking(mainDispatcher) { insertSelection(values) }
+        return runBlocking("$TAG#insert", mainDispatcher) { insertSelection(values) }
     }
 
     override fun query(
@@ -142,7 +142,7 @@
         selectionArgs: Array<out String>?,
         sortOrder: String?,
     ): Cursor? {
-        return runBlocking(mainDispatcher) {
+        return runBlocking("$TAG#query", mainDispatcher) {
             when (uriMatcher.match(uri)) {
                 MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
                 MATCH_CODE_ALL_SLOTS -> querySlots()
@@ -172,7 +172,7 @@
             throw UnsupportedOperationException()
         }
 
-        return runBlocking(mainDispatcher) { deleteSelection(uri, selectionArgs) }
+        return runBlocking("$TAG#delete", mainDispatcher) { deleteSelection(uri, selectionArgs) }
     }
 
     override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index a1f0e77..2b1cdc2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -35,6 +35,8 @@
 import static android.view.WindowManager.TransitionOldType;
 import static android.view.WindowManager.TransitionType;
 
+import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
+
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -76,13 +78,13 @@
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.power.shared.model.ScreenPowerState;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
 import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.power.shared.model.ScreenPowerState;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -200,7 +202,7 @@
     // Wrap Keyguard going away animation.
     // Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
     public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator,
-        final IRemoteAnimationRunner runner, final boolean lockscreenLiveWallpaperEnabled) {
+            final IRemoteAnimationRunner runner) {
         return new IRemoteTransition.Stub() {
 
             @GuardedBy("mLeashMap")
@@ -234,9 +236,8 @@
                     }
                 }
                 initAlphaForAnimationTargets(t, apps);
-                if (lockscreenLiveWallpaperEnabled) {
-                    initAlphaForAnimationTargets(t, wallpapers);
-                }
+                initAlphaForAnimationTargets(t, wallpapers);
+
                 t.apply();
 
                 runner.onAnimationStart(
@@ -269,6 +270,11 @@
                 }
             }
 
+            @Override
+            public void onTransitionConsumed(IBinder transition, boolean aborted)
+                    throws RemoteException {
+            }
+
             private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
                     @NonNull RemoteAnimationTarget[] targets) {
                 for (RemoteAnimationTarget target : targets) {
@@ -594,11 +600,18 @@
             mKeyguardViewMediator.setSwitchingUser(switching);
         }
 
+        /**
+         * @deprecated This binder call is not listened to anymore. Instead the current user is
+         * tracked in SelectedUserInteractor.getSelectedUserId()
+         */
         @Override // Binder interface
+        @Deprecated
         public void setCurrentUser(int userId) {
-            trace("setCurrentUser userId=" + userId);
+            trace("Deprecated/NOT USED: setCurrentUser userId=" + userId);
             checkPermission();
-            mKeyguardViewMediator.setCurrentUser(userId);
+            if (!mFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+                mKeyguardViewMediator.setCurrentUser(userId);
+            }
         }
 
         @Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index fde92b8..c8c06ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -451,7 +451,8 @@
         if (!keyguardStateController.isKeyguardGoingAway &&
                 willUnlockWithInWindowLauncherAnimations) {
             try {
-                launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+                launcherUnlockController?.setUnlockAmount(1f,
+                        biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
             } catch (e: DeadObjectException) {
                 Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
                         "onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
@@ -706,8 +707,7 @@
                 return@postDelayed
             }
 
-            if ((wallpaperTargets?.isNotEmpty() == true) &&
-                    wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            if ((wallpaperTargets?.isNotEmpty() == true)) {
                 fadeInWallpaper()
                 hideKeyguardViewAfterRemoteAnimation()
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 86bf368..c56dfde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.view.LayoutInflater
 import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.KeyguardStatusViewController
 import com.android.keyguard.LockIconView
@@ -27,6 +28,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -43,8 +45,10 @@
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -69,8 +73,11 @@
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
     private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
-    private val lockIconViewController: LockIconViewController,
+    private val lockIconViewController: Lazy<LockIconViewController>,
     private val shadeInteractor: ShadeInteractor,
+    private val interactionJankMonitor: InteractionJankMonitor,
+    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
+    private val vibratorHelper: VibratorHelper,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -125,7 +132,9 @@
         val indicationArea = KeyguardIndicationArea(context, null)
         keyguardIndicationController.setIndicationArea(indicationArea)
 
-        lockIconViewController.setLockIconView(LockIconView(context, null))
+        if (!featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+            lockIconViewController.get().setLockIconView(LockIconView(context, null))
+        }
     }
 
     private fun bindKeyguardRootView() {
@@ -140,6 +149,9 @@
                 keyguardStateController,
                 shadeInteractor,
                 { keyguardStatusViewController!!.getClockController() },
+                interactionJankMonitor,
+                deviceEntryHapticsInteractor,
+                vibratorHelper,
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 39742a0..4e6a872 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -38,6 +38,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
 import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -163,6 +164,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
@@ -182,8 +184,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
-
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -617,6 +617,9 @@
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             synchronized (KeyguardViewMediator.this) {
+                if (mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+                    notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
+                }
                 resetKeyguardDonePendingLocked();
                 dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
@@ -742,7 +745,7 @@
 
         @Override
         public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
-            final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            final int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mLockPatternUtils.isSecure(currentUser)) {
                 mLockPatternUtils.getDevicePolicyManager().reportFailedBiometricAttempt(
                         currentUser);
@@ -760,7 +763,7 @@
 
         @Override
         public void onTrustChanged(int userId) {
-            if (userId == KeyguardUpdateMonitor.getCurrentUser()) {
+            if (userId == mSelectedUserInteractor.getSelectedUserId()) {
                 synchronized (KeyguardViewMediator.this) {
                     notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
                 }
@@ -769,7 +772,7 @@
 
         @Override
         public void onStrongAuthStateChanged(int userId) {
-            if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+            if (mLockPatternUtils.isUserInLockdown(mSelectedUserInteractor.getSelectedUserId())) {
                 doKeyguardLocked(null);
             }
         }
@@ -784,7 +787,7 @@
 
         @Override
         public void keyguardDone(int targetUserId) {
-            if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
+            if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
                 return;
             }
             if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -807,7 +810,7 @@
         public void keyguardDonePending(int targetUserId) {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
             if (DEBUG) Log.d(TAG, "keyguardDonePending");
-            if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
+            if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
                 Trace.endSection();
                 return;
             }
@@ -888,7 +891,7 @@
 
         @Override
         public int getBouncerPromptReason() {
-            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            int currentUser = mSelectedUserInteractor.getSelectedUserId();
             boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
             boolean biometricsEnrolled =
                     mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
@@ -1316,6 +1319,7 @@
 
     private DeviceConfigProxy mDeviceConfig;
     private DozeParameters mDozeParameters;
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -1396,7 +1400,8 @@
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
-            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1436,6 +1441,7 @@
                     mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
                 }));
         mDozeParameters = dozeParameters;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         mStatusBarStateController = statusBarStateController;
         statusBarStateController.addCallback(this);
@@ -1493,25 +1499,27 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
+        if (!mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+            KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
+        }
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
         if (isKeyguardServiceEnabled()) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
+                            mSelectedUserInteractor.getSelectedUserId()),
+                    true /* forceCallbacks */);
         } else {
             // The system's keyguard is disabled or missing.
             setShowingLocked(false /* showing */, true /* forceCallbacks */);
         }
 
-        boolean isLLwpEnabled = getWallpaperManager().isLockscreenLiveWallpaperEnabled();
         mKeyguardTransitions.register(
-                KeyguardService.wrap(this, getExitAnimationRunner(), isLLwpEnabled),
-                KeyguardService.wrap(this, getOccludeAnimationRunner(), isLLwpEnabled),
-                KeyguardService.wrap(this, getOccludeByDreamAnimationRunner(), isLLwpEnabled),
-                KeyguardService.wrap(this, getUnoccludeAnimationRunner(), isLLwpEnabled));
+                KeyguardService.wrap(this, getExitAnimationRunner()),
+                KeyguardService.wrap(this, getOccludeAnimationRunner()),
+                KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()),
+                KeyguardService.wrap(this, getUnoccludeAnimationRunner()));
 
         final ContentResolver cr = mContext.getContentResolver();
 
@@ -1622,11 +1630,11 @@
             // Lock immediately based on setting if secure (user has a pin/pattern/password).
             // This also "locks" the device when not secure to provide easy access to the
             // camera while preventing unwanted input.
-            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            int currentUser = mSelectedUserInteractor.getSelectedUserId();
             final boolean lockImmediately =
                     mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)
                             || !mLockPatternUtils.isSecure(currentUser);
-            long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
+            long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId());
             mLockLater = false;
             if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
                 // If we are going to sleep but the keyguard is showing (and will continue to be
@@ -1807,7 +1815,7 @@
     }
 
     private void doKeyguardLaterLocked() {
-        long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
+        long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId());
         if (timeout == 0) {
             doKeyguardLocked(null);
         } else {
@@ -1916,7 +1924,7 @@
 
     private void maybeSendUserPresentBroadcast() {
         if (mSystemReady && mLockPatternUtils.isLockScreenDisabled(
-                KeyguardUpdateMonitor.getCurrentUser())) {
+                mSelectedUserInteractor.getSelectedUserId())) {
             // Lock screen is disabled because the user has set the preference to "None".
             // In this case, send out ACTION_USER_PRESENT here instead of in
             // handleKeyguardDone()
@@ -1925,7 +1933,7 @@
             // Skipping the lockscreen because we're not yet provisioned, but we still need to
             // notify the StrongAuthTracker that it's now safe to run trust agents, in case the
             // user sets a credential later.
-            mLockPatternUtils.userPresent(KeyguardUpdateMonitor.getCurrentUser());
+            mLockPatternUtils.userPresent(mSelectedUserInteractor.getSelectedUserId());
         }
     }
 
@@ -1966,7 +1974,8 @@
             mExternallyEnabled = enabled;
 
             if (!enabled && mShowing) {
-                if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+                if (mLockPatternUtils.isUserInLockdown(
+                        mSelectedUserInteractor.getSelectedUserId())) {
                     Log.d(TAG, "keyguardEnabled(false) overridden by user lockdown");
                     return;
                 }
@@ -2197,7 +2206,8 @@
     private void doKeyguardLocked(Bundle options) {
         // if another app is disabling us, don't show
         if (!mExternallyEnabled
-                && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+                && !mLockPatternUtils.isUserInLockdown(
+                        mSelectedUserInteractor.getSelectedUserId())) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
 
             mNeedToReshowWhenReenabled = true;
@@ -2253,7 +2263,7 @@
         }
 
         boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
-        if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+        if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId())
                 && !lockedOrMissing && !forceShow) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
             return;
@@ -2384,7 +2394,7 @@
     }
 
     public boolean isSecure() {
-        return isSecure(KeyguardUpdateMonitor.getCurrentUser());
+        return isSecure(mSelectedUserInteractor.getSelectedUserId());
     }
 
     public boolean isSecure(int userId) {
@@ -2605,7 +2615,7 @@
      */
     private void handleKeyguardDone() {
         Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
-        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        final int currentUser = mSelectedUserInteractor.getSelectedUserId();
         mUiBgExecutor.execute(() -> {
             if (mLockPatternUtils.isSecure(currentUser)) {
                 mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
@@ -2631,7 +2641,7 @@
     private void sendUserPresentBroadcast() {
         synchronized (this) {
             if (mBootCompleted) {
-                int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+                int currentUserId = mSelectedUserInteractor.getSelectedUserId();
                 final UserHandle currentUser = new UserHandle(currentUserId);
                 final UserManager um = (UserManager) mContext.getSystemService(
                         Context.USER_SERVICE);
@@ -2679,7 +2689,7 @@
     private void playSound(int soundId) {
         if (soundId == 0) return;
         int lockscreenSoundsEnabled = mSystemSettings.getIntForUser(LOCKSCREEN_SOUNDS_ENABLED, 1,
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         if (lockscreenSoundsEnabled == 1) {
 
             mLockSounds.stop(mLockSoundStreamId);
@@ -2732,7 +2742,7 @@
      */
     private void handleShow(Bundle options) {
         Trace.beginSection("KeyguardViewMediator#handleShow");
-        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        final int currentUser = mSelectedUserInteractor.getSelectedUserId();
         if (mLockPatternUtils.isSecure(currentUser)) {
             mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
         }
@@ -2787,7 +2797,7 @@
      * Schedule 4-hour idle timeout for non-strong biometrics when the device is locked
      */
     private void scheduleNonStrongBiometricIdleTimeout() {
-        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        final int currentUser = mSelectedUserInteractor.getSelectedUserId();
         // If unlocking with non-strong (i.e. weak or convenience) biometrics is possible, schedule
         // 4hr idle timeout after which non-strong biometrics can't be used to unlock device until
         // unlocking with strong biometric or primary auth (i.e. PIN/pattern/password)
@@ -3378,7 +3388,8 @@
             if (forceClearFlags) {
                 try {
                     mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
-                            mContext.getPackageName(), mUserTracker.getUserId());
+                            mContext.getPackageName(),
+                            mSelectedUserInteractor.getSelectedUserId(true));
                 } catch (RemoteException e) {
                     Log.d(TAG, "Failed to force clear flags", e);
                 }
@@ -3405,7 +3416,8 @@
 
             try {
                 mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
-                        mContext.getPackageName(), mUserTracker.getUserId());
+                        mContext.getPackageName(),
+                        mSelectedUserInteractor.getSelectedUserId(true));
             } catch (RemoteException e) {
                 Log.d(TAG, "Failed to set disable flags: " + flags, e);
             }
@@ -3728,7 +3740,8 @@
             for (int i = size - 1; i >= 0; i--) {
                 IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
                 try {
-                    callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser());
+                    callback.onShowingStateChanged(showing,
+                            mSelectedUserInteractor.getSelectedUserId());
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to call onShowingStateChanged", e);
                     if (e instanceof DeadObjectException) {
@@ -3771,10 +3784,11 @@
             mKeyguardStateCallbacks.add(callback);
             try {
                 callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
-                callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser());
+                callback.onShowingStateChanged(mShowing,
+                        mSelectedUserInteractor.getSelectedUserId());
                 callback.onInputRestrictedStateChanged(mInputRestricted);
                 callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
-                        KeyguardUpdateMonitor.getCurrentUser()));
+                        mSelectedUserInteractor.getSelectedUserId()));
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 41bde91..8b93b17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -38,8 +38,6 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
-import com.android.systemui.communal.data.repository.CommunalRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -70,6 +68,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
@@ -95,8 +94,6 @@
         KeyguardStatusViewComponent.class,
         KeyguardUserSwitcherComponent.class},
         includes = {
-            CommunalRepositoryModule.class,
-            CommunalWidgetRepositoryModule.class,
             FalsingModule.class,
             KeyguardDataQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
@@ -154,7 +151,8 @@
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
-            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
+            SelectedUserInteractor selectedUserInteractor) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -198,7 +196,8 @@
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
                 systemPropertiesHelper,
-                wmLockscreenVisibilityManager);
+                wmLockscreenVisibilityManager,
+                selectedUserInteractor);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index e8740a4..654f2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -90,7 +90,7 @@
      * If the current user can use face auth to enter the device. This is true when the user has
      * face auth enrolled, and is enabled in settings/device policy.
      */
-    val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+    val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
 
     /**
      * If the current user can use face auth to enter the device right now. This is true when
@@ -348,10 +348,11 @@
             .and(isFingerprintBiometricAllowed)
             .stateIn(scope, SharingStarted.Eagerly, false)
 
-    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> =
+    override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> =
         isFaceAuthenticationEnabled
             .and(isFaceEnrolled)
             .and(mobileConnectionsRepository.isAnySimSecure.isFalse())
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =
         isFaceAuthEnrolledAndEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 3cdff76..6a0d595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -56,6 +56,10 @@
 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
@@ -78,10 +82,6 @@
 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
@@ -368,10 +368,12 @@
         return arrayOf(
             Pair(
                 and(
-                    displayStateInteractor.isDefaultDisplayOff,
-                    keyguardTransitionInteractor.isFinishedInStateWhere(
-                            KeyguardState::deviceIsAwakeInState),
-                ).isFalse(),
+                        displayStateInteractor.isDefaultDisplayOff,
+                        keyguardTransitionInteractor.isFinishedInStateWhere(
+                            KeyguardState::deviceIsAwakeInState
+                        ),
+                    )
+                    .isFalse(),
                 // this can happen if an app is requesting for screen off, the display can
                 // turn off without wakefulness.isStartingToSleepOrAsleep calls
                 "displayIsNotOffWhileFullyTransitionedToAwake",
@@ -381,10 +383,7 @@
                 "isFaceAuthEnrolledAndEnabled"
             ),
             Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
-            Pair(
-                powerInteractor.isAsleep.isFalse(),
-                "deviceNotAsleep"
-            ),
+            Pair(powerInteractor.isAsleep.isFalse(), "deviceNotAsleep"),
             Pair(
                 keyguardInteractor.isSecureCameraActive
                     .isFalse()
@@ -430,10 +429,15 @@
     private val faceAuthCallback =
         object : FaceManager.AuthenticationCallback() {
             override fun onAuthenticationFailed() {
-                _authenticationStatus.value = FailedFaceAuthenticationStatus()
                 _isAuthenticated.value = false
                 faceAuthLogger.authenticationFailed()
-                onFaceAuthRequestCompleted()
+                if (!_isLockedOut.value) {
+                    // onAuthenticationError gets invoked before onAuthenticationFailed when the
+                    // last auth attempt locks out face authentication.
+                    // Skip updating the authentication status in such a scenario.
+                    _authenticationStatus.value = FailedFaceAuthenticationStatus()
+                    onFaceAuthRequestCompleted()
+                }
             }
 
             override fun onAuthenticationAcquired(acquireInfo: Int) {
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 a4a3126..4d26466 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
@@ -31,7 +31,6 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
-import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DismissAction
@@ -41,9 +40,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
-import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -97,6 +93,12 @@
     val isKeyguardOccluded: Flow<Boolean>
 
     /**
+     * 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>
+
+    /**
      * Observable for the signal that keyguard is about to go away.
      *
      * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
@@ -111,6 +113,8 @@
     /** Is the always-on display available to be used? */
     val isAodAvailable: Flow<Boolean>
 
+    fun setAodAvailable(value: Boolean)
+
     /**
      * Observable for whether we are in doze state.
      *
@@ -160,6 +164,8 @@
     /** Observable for biometric unlock modes */
     val biometricUnlockState: Flow<BiometricUnlockModel>
 
+    fun setBiometricUnlockState(value: BiometricUnlockModel)
+
     /** Approximate location on the screen of the fingerprint sensor. */
     val fingerprintSensorLocation: Flow<Point?>
 
@@ -240,12 +246,9 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
-    screenLifecycle: ScreenLifecycle,
-    biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
-    private val dozeParameters: DozeParameters,
     private val authController: AuthController,
     private val dreamOverlayCallbackController: DreamOverlayCallbackController,
     @Main private val mainDispatcher: CoroutineDispatcher,
@@ -303,24 +306,12 @@
             }
             .distinctUntilChanged()
 
-    override val isAodAvailable: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    DozeParameters.Callback {
-                        trySendWithFailureLogging(
-                            dozeParameters.alwaysOn,
-                            TAG,
-                            "updated isAodAvailable"
-                        )
-                    }
+    private val _isAodAvailable = MutableStateFlow(false)
+    override val isAodAvailable: Flow<Boolean> = _isAodAvailable.asStateFlow()
 
-                dozeParameters.addCallback(callback)
-                // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
-
-                awaitClose { dozeParameters.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
+    override fun setAodAvailable(value: Boolean) {
+        _isAodAvailable.value = value
+    }
 
     override val isKeyguardOccluded: Flow<Boolean> =
         conflatedCallbackFlow {
@@ -347,6 +338,44 @@
             }
             .distinctUntilChanged()
 
+    override val isKeyguardUnlocked: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardStateController.Callback {
+                        override fun onUnlockedChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isKeyguardUnlocked due to onUnlockedChanged"
+                            )
+                        }
+
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
+                            )
+                        }
+                    }
+
+                keyguardStateController.addCallback(callback)
+                // Adding the callback does not send an initial update.
+                trySendWithFailureLogging(
+                    keyguardStateController.isUnlocked,
+                    TAG,
+                    "initial isKeyguardUnlocked"
+                )
+
+                awaitClose { keyguardStateController.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+            .stateIn(
+                scope,
+                SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
             object : KeyguardStateController.Callback {
@@ -506,30 +535,11 @@
                 statusBarStateIntToObject(statusBarStateController.state)
             )
 
-    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
-        fun dispatchUpdate() {
-            trySendWithFailureLogging(
-                biometricModeIntToObject(biometricUnlockController.mode),
-                TAG,
-                "biometric mode"
-            )
-        }
+    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    override val biometricUnlockState = _biometricUnlockState.asStateFlow()
 
-        val callback =
-            object : BiometricUnlockController.BiometricUnlockEventsListener {
-                override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
-                    dispatchUpdate()
-                }
-
-                override fun onResetMode() {
-                    dispatchUpdate()
-                }
-            }
-
-        biometricUnlockController.addListener(callback)
-        dispatchUpdate()
-
-        awaitClose { biometricUnlockController.removeListener(callback) }
+    override fun setBiometricUnlockState(value: BiometricUnlockModel) {
+        _biometricUnlockState.value = value
     }
 
     override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
@@ -662,20 +672,6 @@
         }
     }
 
-    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
-        return when (value) {
-            0 -> BiometricUnlockModel.NONE
-            1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
-            2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            3 -> BiometricUnlockModel.SHOW_BOUNCER
-            4 -> BiometricUnlockModel.ONLY_WAKE
-            5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
-            6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-            7 -> BiometricUnlockModel.DISMISS_BOUNCER
-            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
-        }
-    }
-
     private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
         return when (state) {
             DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 2f80106..5659623 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -32,6 +33,11 @@
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
 
     @Binds
+    @IntoMap
+    @ClassKey(SideFpsProgressBarViewBinder::class)
+    fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable
+
+    @Binds
     fun keyguardSurfaceBehindRepository(
         impl: KeyguardSurfaceBehindRepositoryImpl
     ): KeyguardSurfaceBehindRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 84cd3ef..3eef6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import java.util.UUID
@@ -48,8 +49,8 @@
  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
  * this repository.
  *
- * To print all transitions to logcat to help with debugging, run this command:
- * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command: adb shell settings
+ * put global systemui/buffer/KeyguardLog VERBOSE
  *
  * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
  */
@@ -73,11 +74,8 @@
     /**
      * Begin a transition from one state to another. Transitions are interruptible, and will issue a
      * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
-     *
-     * When canceled, there are two options: to continue from the current position of the prior
-     * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
      */
-    fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
+    fun startTransition(info: TransitionInfo): UUID?
 
     /**
      * Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -138,10 +136,7 @@
         )
     }
 
-    override fun startTransition(
-        info: TransitionInfo,
-        resetIfCanceled: Boolean,
-    ): UUID? {
+    override fun startTransition(info: TransitionInfo): UUID? {
         if (lastStep.from == info.from && lastStep.to == info.to) {
             Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
             return null
@@ -149,10 +144,10 @@
         val startingValue =
             if (lastStep.transitionState != TransitionState.FINISHED) {
                 Log.i(TAG, "Transition still active: $lastStep, canceling")
-                if (resetIfCanceled) {
-                    0f
-                } else {
-                    lastStep.value
+                when (info.modeOnCanceled) {
+                    TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
+                    TransitionModeOnCanceled.RESET -> 0f
+                    TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value
                 }
             } else {
                 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
new file mode 100644
index 0000000..cb003a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
@@ -0,0 +1,42 @@
+package com.android.systemui.keyguard.domain.interactor
+
+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.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
+import javax.inject.Inject
+
+@SysUISingleton
+class BiometricUnlockInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+) {
+
+    fun setBiometricUnlockState(@WakeAndUnlockMode unlockStateInt: Int) {
+        val state = biometricModeIntToObject(unlockStateInt)
+        keyguardRepository.setBiometricUnlockState(state)
+    }
+
+    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+        return when (value) {
+            MODE_NONE -> BiometricUnlockModel.NONE
+            MODE_WAKE_AND_UNLOCK -> BiometricUnlockModel.WAKE_AND_UNLOCK
+            MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            MODE_SHOW_BOUNCER -> BiometricUnlockModel.SHOW_BOUNCER
+            MODE_ONLY_WAKE -> BiometricUnlockModel.ONLY_WAKE
+            MODE_UNLOCK_COLLAPSING -> BiometricUnlockModel.UNLOCK_COLLAPSING
+            MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            MODE_DISMISS_BOUNCER -> BiometricUnlockModel.DISMISS_BOUNCER
+            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 0c898be..af1802f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -28,6 +28,10 @@
     private val keyguardRepository: KeyguardRepository,
 ) {
 
+    fun setAodAvailable(value: Boolean) {
+        keyguardRepository.setAodAvailable(value)
+    }
+
     fun setIsDozing(isDozing: Boolean) {
         keyguardRepository.setIsDozing(isDozing)
     }
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 d06f31f..7e360cf 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
@@ -26,13 +26,13 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.animation.Interpolators
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 
 @SysUISingleton
 class FromAlternateBouncerTransitionInteractor
@@ -130,11 +130,16 @@
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
-            duration = TRANSITION_DURATION_MS.inWholeMilliseconds
+            duration =
+                when (toState) {
+                    KeyguardState.GONE -> TO_GONE_DURATION
+                    else -> TRANSITION_DURATION_MS
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
         val TRANSITION_DURATION_MS = 300.milliseconds
+        val TO_GONE_DURATION = 500.milliseconds
     }
 }
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 38eb730..a331a66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,12 +24,14 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 @SysUISingleton
 class FromAodTransitionInteractor
@@ -64,8 +66,21 @@
                 )
                 .collect { (_, lastStartedStep, occluded) ->
                     if (lastStartedStep.to == KeyguardState.AOD) {
-                        startTransitionTo(
+                        val toState =
                             if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+                        val modeOnCanceled =
+                            if (
+                                toState == KeyguardState.LOCKSCREEN &&
+                                    lastStartedStep.from == KeyguardState.LOCKSCREEN
+                            ) {
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.LAST_VALUE
+                            }
+
+                        startTransitionTo(
+                            toState = toState,
+                            modeOnCanceled = modeOnCanceled,
                         )
                     }
                 }
@@ -86,15 +101,19 @@
                 }
         }
     }
-
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
-            duration = TRANSITION_DURATION_MS
+            duration =
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 500L
+        val TO_LOCKSCREEN_DURATION = 500.milliseconds
+        private val DEFAULT_DURATION = 500.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 75aa4b60f..ca882e5 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
@@ -29,9 +29,7 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -64,29 +62,14 @@
 
     private fun listenForDreamingToOccluded() {
         scope.launch {
-            keyguardInteractor.isDreaming
-                // Add a slight delay, as dreaming and occluded events will arrive with a small gap
-                // in time. This prevents a transition to OCCLUSION happening prematurely.
-                .onEach { delay(50) }
-                .sample(
-                    combine(
-                        keyguardInteractor.isKeyguardOccluded,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair,
-                    ),
-                    ::toTriple
-                )
-                .collect { (isDreaming, isOccluded, lastStartedTransition) ->
+            combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple)
+                .collect { (isOccluded, isDreaming, lastStartedTransition) ->
                     if (
                         isOccluded &&
                             !isDreaming &&
-                            (lastStartedTransition.to == KeyguardState.DREAMING ||
-                                lastStartedTransition.to == KeyguardState.LOCKSCREEN)
+                            lastStartedTransition.to == KeyguardState.DREAMING
                     ) {
-                        // At the moment, checking for LOCKSCREEN state above provides a corrective
-                        // action. There's no great signal to determine when the dream is ending
-                        // and a transition to OCCLUDED is beginning directly. For now, the solution
-                        // is DREAMING->LOCKSCREEN->OCCLUDED
                         startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
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 ad51e74..eace0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
@@ -114,7 +115,9 @@
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
                         startTransitionTo(
-                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+                            toState =
+                                if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
@@ -127,6 +130,7 @@
             duration =
                 when (toState) {
                     KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    KeyguardState.AOD -> TO_AOD_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -134,5 +138,6 @@
     companion object {
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
+        val TO_AOD_DURATION = 1100.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index ffa1a49..95ac0d8 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
@@ -27,22 +27,23 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.TraceUtils.Companion.launch
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.launch
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
@@ -135,7 +136,7 @@
 
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToDreaming") {
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
@@ -168,7 +169,7 @@
     }
 
     private fun listenForLockscreenToPrimaryBouncer() {
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
@@ -183,7 +184,7 @@
     }
 
     private fun listenForLockscreenToAlternateBouncer() {
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
             keyguardInteractor.alternateBouncerShowing
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
@@ -201,7 +202,7 @@
     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
     private fun listenForLockscreenToPrimaryBouncerDragging() {
         var transitionId: UUID? = null
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
             shadeRepository.shadeModel
                 .sample(
                     combine(
@@ -286,7 +287,7 @@
             return
         }
 
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToGone") {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
@@ -303,7 +304,7 @@
             return
         }
 
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToGoneDragging") {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
@@ -316,18 +317,11 @@
     }
 
     private fun listenForLockscreenToOccluded() {
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToOccluded") {
             keyguardInteractor.isKeyguardOccluded
-                .sample(
-                    combine(
-                        transitionInteractor.startedKeyguardState,
-                        keyguardInteractor.isDreaming,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { (isOccluded, keyguardState, isDreaming) ->
-                    if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+                .sample(transitionInteractor.startedKeyguardState, ::Pair)
+                .collect { (isOccluded, keyguardState) ->
+                    if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
                         startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
@@ -335,7 +329,7 @@
     }
 
     private fun listenForLockscreenToAodOrDozing() {
-        scope.launch {
+        scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
             powerInteractor.isAsleep
                 .sample(
                     combine(
@@ -347,8 +341,20 @@
                 )
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
-                        startTransitionTo(
+                        val toState =
                             if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+                        val modeOnCanceled =
+                            if (
+                                toState == KeyguardState.AOD &&
+                                    lastStartedStep.from == KeyguardState.AOD
+                            ) {
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.LAST_VALUE
+                            }
+                        startTransitionTo(
+                            toState = toState,
+                            modeOnCanceled = modeOnCanceled,
                         )
                     }
                 }
@@ -362,14 +368,17 @@
                 when (toState) {
                     KeyguardState.DREAMING -> TO_DREAMING_DURATION
                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+                    KeyguardState.AOD -> TO_AOD_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
     }
 
     companion object {
+        const val TAG = "FromLockscreenTransitionInteractor"
         private val DEFAULT_DURATION = 400.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
         val TO_OCCLUDED_DURATION = 450.milliseconds
+        val TO_AOD_DURATION = 500.milliseconds
     }
 }
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 ad2ec69..24b6661 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,7 +18,6 @@
 
 import android.animation.ValueAnimator
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
@@ -26,20 +25,22 @@
 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.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.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 
 @SysUISingleton
 class FromPrimaryBouncerTransitionInteractor
@@ -51,6 +52,7 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
+    private val selectedUserInteractor: SelectedUserInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
@@ -132,7 +134,7 @@
                 .collect {
                     (
                         isBouncerShowing,
-                            isAwake,
+                        isAwake,
                         lastStartedTransitionStep,
                         occluded,
                         isActiveDreamLockscreenHosted) ->
@@ -162,8 +164,7 @@
                     ),
                     ::toQuad
                 )
-                .collect {
-                    (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
+                .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
                     ->
                     if (
                         !isBouncerShowing &&
@@ -181,25 +182,24 @@
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                    .sample(
-                            combine(
-                                    keyguardInteractor.isActiveDreamLockscreenHosted,
-                                    transitionInteractor.startedKeyguardTransitionStep,
-                                    ::Pair
-                            ),
-                            ::toTriple
-                    )
-                    .collect { (isBouncerShowing,
-                                       isActiveDreamLockscreenHosted,
-                                       lastStartedTransitionStep) ->
-                        if (
-                                !isBouncerShowing &&
-                                isActiveDreamLockscreenHosted &&
-                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
-                        ) {
-                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-                        }
+                .sample(
+                    combine(
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect {
+                    (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+                    if (
+                        !isBouncerShowing &&
+                            isActiveDreamLockscreenHosted &&
+                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+                    ) {
+                        startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                     }
+                }
         }
     }
 
@@ -221,7 +221,7 @@
                     ) {
                         val securityMode =
                             keyguardSecurityModel.getSecurityMode(
-                                KeyguardUpdateMonitor.getCurrentUser()
+                                selectedUserInteractor.getSelectedUserId()
                             )
                         // IME for password requires a slightly faster animation
                         val duration =
@@ -237,7 +237,7 @@
                                 getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
                                     this.duration = duration.inWholeMilliseconds
                                 },
-                            resetIfCancelled = true
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index cab6928..628e912 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -40,11 +40,11 @@
 @Inject
 constructor(
     trustRepository: TrustRepository,
-    val keyguardRepository: KeyguardRepository,
-    val primaryBouncerInteractor: PrimaryBouncerInteractor,
-    val alternateBouncerInteractor: AlternateBouncerInteractor,
-    val powerInteractor: PowerInteractor,
-    val userInteractor: UserInteractor,
+    private val keyguardRepository: KeyguardRepository,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
+    powerInteractor: PowerInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /*
      * Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -82,7 +82,7 @@
      */
     private val primaryAuthenticated: Flow<Unit> =
         primaryBouncerInteractor.keyguardAuthenticatedPrimaryAuth
-            .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+            .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() }
             .map {} // map to Unit
 
     /*
@@ -92,7 +92,7 @@
      */
     private val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Unit> =
         primaryBouncerInteractor.userRequestedBouncerWhenAlreadyAuthenticated
-            .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+            .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() }
             .map {} // map to Unit
 
     /** Updates when keyguardDone should be requested. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 89aca76..85b0f4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -41,6 +41,9 @@
     /** Whether face auth is in lock out state. */
     fun isLockedOut(): Boolean
 
+    /** Whether face auth is enrolled and enabled for the current user */
+    fun isFaceAuthEnabledAndEnrolled(): Boolean
+
     /**
      * Register listener for use from code that cannot use [authenticationStatus] or
      * [detectionStatus]
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 6e19fdb..eaec0d4 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
@@ -30,7 +30,6 @@
 import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -39,7 +38,6 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
@@ -49,6 +47,8 @@
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
@@ -64,8 +64,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
-import javax.inject.Provider
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -79,7 +77,6 @@
     private val powerInteractor: PowerInteractor,
     featureFlags: FeatureFlags,
     sceneContainerFlags: SceneContainerFlags,
-    deviceEntryRepository: DeviceEntryRepository,
     bouncerRepository: KeyguardBouncerRepository,
     configurationRepository: ConfigurationRepository,
     shadeRepository: ShadeRepository,
@@ -148,9 +145,7 @@
             .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
                 isDreaming && isDozeOff(dozeTransitionModel.to)
             }
-            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake ->
-                isAbleToDream && isAwake
-            }
+            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
             .flatMapLatest { isAbleToDream ->
                 flow {
                     delay(50)
@@ -163,7 +158,7 @@
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
 
     /** Whether the keyguard is unlocked or not. */
-    val isKeyguardUnlocked: Flow<Boolean> = deviceEntryRepository.isUnlocked
+    val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
 
     /** Whether the keyguard is occluded (covered by an activity). */
     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -217,11 +212,8 @@
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
     /** Notifies when a new configuration is set */
-    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
-
-    /** Represents the current state of the KeyguardRootView visibility */
-    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
-        repository.keyguardRootViewVisibility
+    val configurationChange: Flow<Unit> =
+        configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
 
     /** The position of the keyguard clock. */
     val clockPosition: Flow<Position> = repository.clockPosition
@@ -235,12 +227,17 @@
                     R.dimen.keyguard_translate_distance_on_swipe_up
                 )
             shadeRepository.shadeModel.map {
-                // On swipe up, translate the keyguard to reveal the bouncer
-                MathUtils.lerp(
-                    translationDistance,
-                    0,
-                    Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
-                )
+                if (it.expansionAmount == 0f) {
+                    // Reset the translation value
+                    0f
+                } else {
+                    // On swipe up, translate the keyguard to reveal the bouncer
+                    MathUtils.lerp(
+                        translationDistance,
+                        0,
+                        Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+                    )
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index ac012f8..55b420b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -21,7 +21,7 @@
 import android.view.KeyEvent
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler.Companion.handleAction
 import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -39,7 +39,6 @@
 constructor(
     private val context: Context,
     private val statusBarStateController: StatusBarStateController,
-    private val keyguardInteractor: KeyguardInteractor,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     private val shadeController: ShadeController,
     private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
@@ -56,13 +55,14 @@
         }
 
         if (event.handleAction()) {
+            if (KeyEvent.isConfirmKey(event.keyCode)) {
+                if (isDeviceAwake()) {
+                    return collapseShadeLockedOrShowPrimaryBouncer()
+                }
+            }
+
             when (event.keyCode) {
                 KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
-                KeyEvent.KEYCODE_SPACE,
-                KeyEvent.KEYCODE_ENTER ->
-                    if (isDeviceAwake()) {
-                        return collapseShadeLockedOrShowPrimaryBouncer()
-                    }
             }
         }
         return false
@@ -111,10 +111,8 @@
                 return true
             }
             StatusBarState.KEYGUARD -> {
-                if (!statusBarKeyguardViewManager.primaryBouncerIsShowing()) {
-                    statusBarKeyguardViewManager.showPrimaryBouncer(true)
-                    return true
-                }
+                statusBarKeyguardViewManager.showPrimaryBouncer(true)
+                return true
             }
         }
         return false
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 de791aa..fe9370f 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
@@ -23,7 +23,6 @@
 import android.content.Intent
 import android.util.Log
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
@@ -44,11 +43,12 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.TraceUtils.Companion.traceAsync
+import com.android.systemui.util.TraceUtils.Companion.withContext
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +59,6 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -417,10 +416,8 @@
     }
 
     private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
-        traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
-            withContext(backgroundDispatcher) {
-                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
-            }
+        withContext("$TAG#isFeatureDisabledByDevicePolicy", backgroundDispatcher) {
+            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
         }
 
     companion object {
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 fbe26de..b0b8577 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
@@ -143,6 +143,11 @@
     val dozingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DOZING, LOCKSCREEN)
 
+    /** Receive all [TransitionStep] matching a filter of [from]->[to] */
+    fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+        return repository.transition(from, to)
+    }
+
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
      * Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index f38bb2b..fbadde6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -43,6 +43,7 @@
     override fun isLockedOut(): Boolean = false
 
     override fun isEnabled() = false
+    override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
 
     override fun registerListener(listener: FaceAuthenticationListener) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 797dec2..2641846 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
@@ -81,6 +82,7 @@
     private val facePropertyRepository: FacePropertyRepository,
     private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
     private val powerInteractor: PowerInteractor,
+    private val biometricSettingsRepository: BiometricSettingsRepository,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -149,7 +151,10 @@
             .onEach {
                 if (it) {
                     faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
-                    repository.setLockedOut(true)
+                    // We don't care about this if face auth is not enabled.
+                    if (isFaceAuthEnabledAndEnrolled()) {
+                        repository.setLockedOut(true)
+                    }
                 }
             }
             .launchIn(applicationScope)
@@ -263,6 +268,9 @@
         }
     }
 
+    override fun isFaceAuthEnabledAndEnrolled(): Boolean =
+        biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value
+
     private fun observeFaceAuthStateUpdates() {
         authenticationStatus
             .onEach { authStatusUpdate ->
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 54c6d5f..7601808 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
@@ -21,6 +21,7 @@
 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.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import kotlinx.coroutines.CoroutineScope
@@ -49,7 +50,7 @@
     fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
-        resetIfCancelled: Boolean = false,
+        modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
     ): UUID? {
         if (
             fromState != transitionInteractor.startedKeyguardState.value &&
@@ -73,8 +74,8 @@
                 fromState,
                 toState,
                 animator,
-            ),
-            resetIfCancelled
+                modeOnCanceled,
+            )
         )
     }
 
@@ -91,8 +92,8 @@
                     // so use the last finishedKeyguardState to determine the overriding FROM state
                     if (finishedKeyguardState == fromState) {
                         startTransitionTo(
-                            KeyguardState.OCCLUDED,
-                            resetIfCancelled = true,
+                            toState = KeyguardState.OCCLUDED,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index ae18681..3c143fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.shared.model
 
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
 import android.hardware.fingerprint.FingerprintManager
 import android.os.SystemClock.elapsedRealtime
 
@@ -39,7 +41,12 @@
 
 /** Fingerprint acquired message. */
 data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
-    FingerprintAuthenticationStatus()
+    FingerprintAuthenticationStatus() {
+
+    val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
+
+    val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD
+}
 
 /** Fingerprint authentication failed message. */
 object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
index bfccf3fe..7a37365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -22,7 +22,13 @@
     val ownerName: String,
     val from: KeyguardState,
     val to: KeyguardState,
-    val animator: ValueAnimator?, // 'null' animator signal manual control
+    /** [null] animator signals manual control, otherwise transition run by the animator */
+    val animator: ValueAnimator?,
+    /**
+     * If the transition resets in the cancellation of another transition, use this mode to
+     * determine how to continue.
+     */
+    val modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
 ) {
     override fun toString(): String =
         "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
new file mode 100644
index 0000000..56f90bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** When canceled, provide different ways to start the next transition. */
+enum class TransitionModeOnCanceled {
+    /** Proceed from the last value. If canceled at .7, start from .7 and end at 1 */
+    LAST_VALUE,
+    /** Start over from 0. If canceled at .7, start from 0 and end at 1 */
+    RESET,
+    /** Reverse the transition. If canceled at .7, start from 1-.7 (0.3) and end at 1 */
+    REVERSE
+}
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
new file mode 100644
index 0000000..e82ea7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object DeviceEntryIconViewBinder {
+
+    /**
+     * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its
+     * background.
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    @JvmStatic
+    fun bind(
+        view: DeviceEntryIconView,
+        viewModel: DeviceEntryIconViewModel,
+        falsingManager: FalsingManager,
+    ) {
+        val iconView = view.iconView
+        val bgView = view.bgView
+        val longPressHandlingView = view.longPressHandlingView
+        longPressHandlingView.listener =
+            object : LongPressHandlingView.Listener {
+                override fun onLongPressDetected(view: View, x: Int, y: Int) {
+                    if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+                        return
+                    }
+                    viewModel.onLongPress()
+                }
+            }
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.iconViewModel.collect { iconViewModel ->
+                        iconView.setImageState(
+                            view.getIconState(iconViewModel.type, iconViewModel.useAodVariant),
+                            /* merge */ false
+                        )
+                        iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint)
+                        iconView.alpha = iconViewModel.alpha
+                        iconView.setPadding(
+                            iconViewModel.padding,
+                            iconViewModel.padding,
+                            iconViewModel.padding,
+                            iconViewModel.padding,
+                        )
+                    }
+                }
+                launch {
+                    viewModel.backgroundViewModel.collect { bgViewModel ->
+                        bgView.alpha = bgViewModel.alpha
+                        bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+                    }
+                }
+                launch {
+                    viewModel.burnInViewModel.collect { burnInViewModel ->
+                        view.translationX = burnInViewModel.x.toFloat()
+                        view.translationY = burnInViewModel.y.toFloat()
+                        view.aodFpDrawable.progress = burnInViewModel.progress
+                    }
+                }
+                launch {
+                    viewModel.isLongPressEnabled.collect { isEnabled ->
+                        longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+                    }
+                }
+                launch {
+                    viewModel.accessibilityDelegateHint.collect { hint ->
+                        view.accessibilityHintType = hint
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index abc30ef..c5a8375 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.doOnEnd
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -402,6 +403,9 @@
                         KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
                     shakeAnimator.interpolator =
                         CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+                    shakeAnimator.doOnEnd {
+                        view.translationX = 0f
+                    }
                     shakeAnimator.start()
 
                     vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index f14552b..87d8164 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -25,20 +25,18 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
 /** Handles keyguard dismissal requests. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardDismissBinder
 @Inject
 constructor(
     private val interactor: KeyguardDismissInteractor,
-    private val userInteractor: UserInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
     private val viewMediatorCallback: ViewMediatorCallback,
     @Application private val scope: CoroutineScope,
     private val keyguardLogger: KeyguardLogger,
@@ -55,11 +53,15 @@
                 when (keyguardDoneTiming) {
                     KeyguardDone.LATER -> {
                         log("keyguardDonePending")
-                        viewMediatorCallback.keyguardDonePending(userInteractor.getSelectedUserId())
+                        viewMediatorCallback.keyguardDonePending(
+                            selectedUserInteractor.getSelectedUserId()
+                        )
                     }
                     else -> {
                         log("keyguardDone")
-                        viewMediatorCallback.keyguardDone(userInteractor.getSelectedUserId())
+                        viewMediatorCallback.keyguardDone(
+                            selectedUserInteractor.getSelectedUserId()
+                        )
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index f0d118c..99025ace 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.doOnEnd
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -240,6 +241,9 @@
                         KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
                     shakeAnimator.interpolator =
                         CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+                    shakeAnimator.doOnEnd {
+                        view.translationX = 0f
+                    }
                     shakeAnimator.start()
 
                     vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
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 ac4ad39..4d5c503 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
@@ -17,24 +17,30 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.annotation.DrawableRes
+import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
 import android.view.ViewGroup
+import android.view.ViewGroup.OnHierarchyChangeListener
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -42,14 +48,13 @@
 import javax.inject.Provider
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
 @ExperimentalCoroutinesApi
 object KeyguardRootViewBinder {
 
-    private var onLayoutChangeListener: OnLayoutChange? = null
-
     @JvmStatic
     fun bind(
         view: ViewGroup,
@@ -60,7 +65,16 @@
         keyguardStateController: KeyguardStateController,
         shadeInteractor: ShadeInteractor,
         clockControllerProvider: Provider<ClockController>?,
+        interactionJankMonitor: InteractionJankMonitor?,
+        deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
+        vibratorHelper: VibratorHelper?,
     ): DisposableHandle {
+        var onLayoutChangeListener: OnLayoutChange? = null
+        val childViews = mutableMapOf<Int, View?>()
+        val statusViewId = R.id.keyguard_status_view
+        val burnInLayerId = R.id.burn_in_layer
+        val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+        val largeClockId = R.id.lockscreen_clock_view_large
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -86,36 +100,74 @@
 
                     if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         launch {
-                            viewModel.translationY.collect { y ->
-                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
-                                burnInLayer.translationY = y
+                            viewModel.burnInLayerVisibility.collect { visibility ->
+                                childViews[burnInLayerId]?.visibility = visibility
+                                // Reset alpha only for the icons, as they currently have their
+                                // own animator
+                                childViews[aodNotificationIconContainerId]?.alpha = 0f
                             }
                         }
-                    }
 
-                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        launch {
+                            viewModel.burnInLayerAlpha.collect { alpha ->
+                                childViews[statusViewId]?.alpha = alpha
+                                childViews[aodNotificationIconContainerId]?.alpha = alpha
+                            }
+                        }
+
+                        launch {
+                            viewModel.lockscreenStateAlpha.collect { alpha ->
+                                childViews[statusViewId]?.alpha = alpha
+                            }
+                        }
+
+                        launch {
+                            viewModel.translationY.collect { y ->
+                                childViews[burnInLayerId]?.translationY = y
+                            }
+                        }
+
                         launch {
                             viewModel.translationX.collect { x ->
-                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
-                                burnInLayer.translationX = x
+                                childViews[burnInLayerId]?.translationX = x
                             }
                         }
-                    }
 
-                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         launch {
                             viewModel.scale.collect { (scale, scaleClockOnly) ->
                                 if (scaleClockOnly) {
-                                    val largeClock =
-                                        view.findViewById<View?>(R.id.lockscreen_clock_view_large)
-                                    largeClock?.let {
+                                    childViews[largeClockId]?.let {
                                         it.scaleX = scale
                                         it.scaleY = scale
                                     }
                                 } else {
-                                    val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
-                                    burnInLayer.scaleX = scale
-                                    burnInLayer.scaleY = scale
+                                    childViews[burnInLayerId]?.scaleX = scale
+                                    childViews[burnInLayerId]?.scaleY = scale
+                                }
+                            }
+                        }
+
+                        interactionJankMonitor?.let { jankMonitor ->
+                            launch {
+                                viewModel.goneToAodTransition.collect {
+                                    when (it.transitionState) {
+                                        TransitionState.STARTED -> {
+                                            val clockId =
+                                                clockControllerProvider?.get()?.config?.id
+                                                    ?: MISSING_CLOCK_ID
+                                            val builder =
+                                                InteractionJankMonitor.Configuration.Builder
+                                                    .withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
+                                                    .setTag(clockId)
+
+                                            jankMonitor.begin(builder)
+                                        }
+                                        TransitionState.CANCELED ->
+                                            jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+                                        TransitionState.FINISHED ->
+                                            jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+                                        TransitionState.RUNNING -> Unit
+                                    }
                                 }
                             }
                         }
@@ -131,42 +183,42 @@
                                 }
                         }
                     }
-                }
 
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                    if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
-                            viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
-                                view.animate().cancel()
-                                val goingToFullShade = visibilityState.goingToFullShade
-                                val statusBarState = visibilityState.statusBarState
-                                val isOcclusionTransitionRunning =
-                                    visibilityState.occlusionTransitionRunning
-                                if (goingToFullShade) {
-                                    view
-                                        .animate()
-                                        .alpha(0f)
-                                        .setStartDelay(
-                                            keyguardStateController.keyguardFadingAwayDelay
+                            deviceEntryHapticsInteractor.playSuccessHaptic
+                                .filter { it }
+                                .collect {
+                                    if (
+                                        featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+                                    ) {
+                                        vibratorHelper.performHapticFeedback(
+                                            view,
+                                            HapticFeedbackConstants.CONFIRM,
                                         )
-                                        .setDuration(
-                                            keyguardStateController.shortenedFadingAwayDuration
-                                        )
-                                        .setInterpolator(Interpolators.ALPHA_OUT)
-                                        .withEndAction { view.visibility = View.GONE }
-                                        .start()
-                                } else if (
-                                    statusBarState == StatusBarState.KEYGUARD ||
-                                        statusBarState == StatusBarState.SHADE_LOCKED
-                                ) {
-                                    view.visibility = View.VISIBLE
-                                    if (!isOcclusionTransitionRunning) {
-                                        view.alpha = 1f
+                                    } else {
+                                        vibratorHelper.vibrateAuthSuccess("device-entry::success")
                                     }
-                                } else {
-                                    view.visibility = View.GONE
+                                    deviceEntryHapticsInteractor.handleSuccessHaptic()
                                 }
-                            }
+                        }
+
+                        launch {
+                            deviceEntryHapticsInteractor.playErrorHaptic
+                                .filter { it }
+                                .collect {
+                                    if (
+                                        featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+                                    ) {
+                                        vibratorHelper.performHapticFeedback(
+                                            view,
+                                            HapticFeedbackConstants.REJECT,
+                                        )
+                                    } else {
+                                        vibratorHelper.vibrateAuthSuccess("device-entry::error")
+                                    }
+                                    deviceEntryHapticsInteractor.handleErrorHaptic()
+                                }
                         }
                     }
                 }
@@ -176,10 +228,26 @@
         onLayoutChangeListener = OnLayoutChange(viewModel)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
 
+        // Views will be added or removed after the call to bind(). This is needed to avoid many
+        // calls to findViewById
+        view.setOnHierarchyChangeListener(
+            object : OnHierarchyChangeListener {
+                override fun onChildViewAdded(parent: View, child: View) {
+                    childViews.put(child.id, child)
+                }
+
+                override fun onChildViewRemoved(parent: View, child: View) {
+                    childViews.remove(child.id)
+                }
+            }
+        )
+
         return object : DisposableHandle {
             override fun dispose() {
                 disposableHandle.dispose()
                 view.removeOnLayoutChangeListener(onLayoutChangeListener)
+                view.setOnHierarchyChangeListener(null)
+                childViews.clear()
             }
         }
     }
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
index f3586ba..2feaa2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
 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
 
 /**
@@ -46,8 +47,8 @@
             constraintLayout: ConstraintLayout,
             viewModel: KeyguardBlueprintViewModel,
             finishedAddViewCallback: () -> Unit
-        ) {
-            constraintLayout.repeatWhenAttached {
+        ): DisposableHandle {
+            return constraintLayout.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
                     launch {
                         viewModel.blueprint.collect { blueprint ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
new file mode 100644
index 0000000..1acea5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.util.kotlin.Quint
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewBinder
+@Inject
+constructor(
+    private val viewModel: SideFpsProgressBarViewModel,
+    private val view: SideFpsProgressBar,
+    @Application private val applicationScope: CoroutineScope,
+    private val sfpsController: dagger.Lazy<SideFpsController>,
+) : CoreStartable {
+
+    override fun start() {
+        applicationScope.launch {
+            viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+                if (enabled) {
+                    launch {
+                        combine(
+                                viewModel.isVisible,
+                                viewModel.sensorLocation,
+                                viewModel.shouldRotate90Degrees,
+                                viewModel.isFingerprintAuthRunning,
+                                viewModel.sensorWidth,
+                                ::Quint
+                            )
+                            .collectLatest {
+                                (visible, location, shouldRotate, fpDetectRunning, sensorWidth) ->
+                                view.updateView(visible, location, shouldRotate, sensorWidth)
+                                // We have to hide the SFPS indicator as the progress bar will
+                                // be shown at the same location
+                                if (visible) {
+                                    sfpsController.get().hideIndicator()
+                                } else if (fpDetectRunning) {
+                                    sfpsController.get().showIndicator()
+                                }
+                            }
+                    }
+                    launch { viewModel.progress.collectLatest { view.setProgress(it) } }
+                } else {
+                    view.hideOverlay()
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
index a0e0da4..475d26f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
@@ -23,7 +23,6 @@
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.UdfpsKeyguardView
 import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
@@ -32,6 +31,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -52,8 +52,6 @@
         fingerprintViewModel: FingerprintViewModel,
         backgroundViewModel: BackgroundViewModel,
     ) {
-        view.useExpandedOverlay(viewModel.useExpandedOverlay())
-
         val layoutInflaterFinishListener =
             AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
                 UdfpsKeyguardInternalViewBinder.bind(
@@ -63,25 +61,21 @@
                     fingerprintViewModel,
                     backgroundViewModel,
                 )
-                if (viewModel.useExpandedOverlay()) {
-                    val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
-                    lp.width = viewModel.sensorBounds.width()
-                    lp.height = viewModel.sensorBounds.height()
-                    val relativeToView =
-                        getBoundsRelativeToView(
-                            inflatedInternalView,
-                            RectF(viewModel.sensorBounds),
-                        )
-                    lp.setMarginsRelative(
-                        relativeToView.left.toInt(),
-                        relativeToView.top.toInt(),
-                        relativeToView.right.toInt(),
-                        relativeToView.bottom.toInt(),
+                val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+                lp.width = viewModel.sensorBounds.width()
+                lp.height = viewModel.sensorBounds.height()
+                val relativeToView =
+                    getBoundsRelativeToView(
+                        inflatedInternalView,
+                        RectF(viewModel.sensorBounds),
                     )
-                    parent!!.addView(inflatedInternalView, lp)
-                } else {
-                    parent!!.addView(inflatedInternalView)
-                }
+                lp.setMarginsRelative(
+                    relativeToView.left.toInt(),
+                    relativeToView.top.toInt(),
+                    relativeToView.right.toInt(),
+                    relativeToView.bottom.toInt(),
+                )
+                parent!!.addView(inflatedInternalView, lp)
             }
         val inflater = AsyncLayoutInflater(view.context)
         inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
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 c6d8ec7..692984a 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
@@ -27,14 +27,15 @@
 import android.os.Bundle
 import android.os.Handler
 import android.os.IBinder
+import android.view.ContextThemeWrapper
 import android.view.Display
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.DisplayInfo
 import android.view.LayoutInflater
 import android.view.SurfaceControlViewHost
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD
 import android.widget.FrameLayout
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isInvisible
@@ -45,6 +46,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
@@ -113,6 +115,7 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val keyguardStateController: KeyguardStateController,
     private val shadeInteractor: ShadeInteractor,
+    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
 ) {
 
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -129,7 +132,7 @@
         bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
     private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS)
     private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY)
-    private val display: Display = displayManager.getDisplay(displayId)
+    private val display: Display? = displayManager.getDisplay(displayId)
 
     private var host: SurfaceControlViewHost
 
@@ -179,7 +182,11 @@
 
     fun render() {
         mainHandler.post {
-            val previewContext = context.createDisplayContext(display)
+            val previewContext =
+                display?.let {
+                    ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
+                }
+                    ?: context
 
             val rootView = FrameLayout(previewContext)
 
@@ -189,16 +196,18 @@
                 setUpBottomArea(rootView)
             }
 
-            val windowContext = context.createWindowContext(display, TYPE_KEYGUARD, null)
-            val windowManagerOfDisplay = windowContext.getSystemService(WindowManager::class.java)
+            var displayInfo: DisplayInfo? = null
+            display?.let {
+                displayInfo = DisplayInfo()
+                it.getDisplayInfo(displayInfo)
+            }
             rootView.measure(
                 View.MeasureSpec.makeMeasureSpec(
-                    windowManagerOfDisplay?.currentWindowMetrics?.bounds?.width()
-                        ?: windowManager.currentWindowMetrics.bounds.width(),
+                    displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(),
                     View.MeasureSpec.EXACTLY
                 ),
                 View.MeasureSpec.makeMeasureSpec(
-                    windowManagerOfDisplay?.currentWindowMetrics?.bounds?.height()
+                    displayInfo?.logicalHeight
                         ?: windowManager.currentWindowMetrics.bounds.height(),
                     View.MeasureSpec.EXACTLY
                 ),
@@ -320,7 +329,7 @@
 
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
-        val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() }
+        val keyguardRootView = KeyguardRootView(previewContext, null)
         disposables.add(
             KeyguardRootViewBinder.bind(
                 keyguardRootView,
@@ -331,6 +340,9 @@
                 keyguardStateController,
                 shadeInteractor,
                 null, // clock provider only needed for burn in
+                null, // jank monitor not required for preview mode
+                null, // device entry haptics not required for preview mode
+                null, // device entry haptics not required for preview mode
             )
         )
         rootView.addView(
@@ -340,26 +352,29 @@
                 FrameLayout.LayoutParams.MATCH_PARENT,
             ),
         )
-        PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
-            if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-                setupShortcuts(keyguardRootView)
-            }
-            setUpUdfps(previewContext, rootView)
 
-            if (!shouldHideClock) {
-                setUpClock(previewContext, rootView)
-                KeyguardPreviewClockViewBinder.bind(
-                    largeClockHostView,
-                    smallClockHostView,
-                    clockViewModel,
-                )
-            }
+        disposables.add(
+            PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
+                if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                    setupShortcuts(keyguardRootView)
+                }
+                setUpUdfps(previewContext, rootView)
 
-            setUpSmartspace(previewContext, rootView)
-            smartSpaceView?.let {
-                KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+                if (!shouldHideClock) {
+                    setUpClock(previewContext, rootView)
+                    KeyguardPreviewClockViewBinder.bind(
+                        largeClockHostView,
+                        smallClockHostView,
+                        clockViewModel,
+                    )
+                }
+
+                setUpSmartspace(previewContext, rootView)
+                smartSpaceView?.let {
+                    KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+                }
             }
-        }
+        )
     }
 
     private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
new file mode 100644
index 0000000..c9e3954
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.content.Context
+import android.graphics.drawable.AnimatedStateListDrawable
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.util.AttributeSet
+import android.util.StateSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.airbnb.lottie.LottieCompositionFactory
+import com.airbnb.lottie.LottieDrawable
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.res.R
+
+class DeviceEntryIconView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet?,
+    defStyleAttrs: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttrs) {
+    val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs)
+    val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg }
+    val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg }
+    val aodFpDrawable: LottieDrawable = LottieDrawable()
+    var accessibilityHintType: AccessibilityHintType = AccessibilityHintType.NONE
+
+    private var animatedIconDrawable: AnimatedStateListDrawable = AnimatedStateListDrawable()
+
+    init {
+        setupIconStates()
+        setupIconTransitions()
+        setupAccessibilityDelegate()
+
+        // Ordering matters. From background to foreground we want:
+        //     bgView, iconView, longpressHandlingView overlay
+        addBgImageView()
+        addIconImageView()
+        addLongpressHandlingView()
+    }
+
+    private fun setupAccessibilityDelegate() {
+        accessibilityDelegate =
+            object : AccessibilityDelegate() {
+                private val accessibilityAuthenticateHint =
+                    AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
+                        resources.getString(R.string.accessibility_authenticate_hint)
+                    )
+                private val accessibilityEnterHint =
+                    AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
+                        resources.getString(R.string.accessibility_enter_hint)
+                    )
+                override fun onInitializeAccessibilityNodeInfo(
+                    v: View,
+                    info: AccessibilityNodeInfo
+                ) {
+                    super.onInitializeAccessibilityNodeInfo(v, info)
+                    when (accessibilityHintType) {
+                        AccessibilityHintType.AUTHENTICATE ->
+                            info.addAction(accessibilityAuthenticateHint)
+                        AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint)
+                        AccessibilityHintType.NONE -> return
+                    }
+                }
+            }
+    }
+
+    private fun setupIconStates() {
+        // Lockscreen States
+        // LOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.LOCK, false),
+            context.getDrawable(R.drawable.ic_lock)!!,
+            R.id.locked,
+        )
+        // UNLOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.UNLOCK, false),
+            context.getDrawable(R.drawable.ic_unlocked)!!,
+            R.id.unlocked,
+        )
+        // FINGERPRINT
+        animatedIconDrawable.addState(
+            getIconState(IconType.FINGERPRINT, false),
+            context.getDrawable(R.drawable.ic_kg_fingerprint)!!,
+            R.id.locked_fp,
+        )
+
+        // AOD states
+        // LOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.LOCK, true),
+            context.getDrawable(R.drawable.ic_lock_aod)!!,
+            R.id.locked_aod,
+        )
+        // UNLOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.UNLOCK, true),
+            context.getDrawable(R.drawable.ic_unlocked_aod)!!,
+            R.id.unlocked_aod,
+        )
+        // FINGERPRINT
+        LottieCompositionFactory.fromRawRes(mContext, R.raw.udfps_aod_fp).addListener { result ->
+            aodFpDrawable.setComposition(result)
+        }
+        animatedIconDrawable.addState(
+            getIconState(IconType.FINGERPRINT, true),
+            aodFpDrawable,
+            R.id.udfps_aod_fp,
+        )
+
+        // WILDCARD: should always be the last state added since any states will match with this
+        // and therefore won't get matched with subsequent states.
+        animatedIconDrawable.addState(
+            StateSet.WILD_CARD,
+            context.getDrawable(R.color.transparent)!!,
+            R.id.no_icon,
+        )
+    }
+
+    private fun setupIconTransitions() {
+        // LockscreenFp <=> LockscreenUnlocked
+        animatedIconDrawable.addTransition(
+            R.id.locked_fp,
+            R.id.unlocked,
+            context.getDrawable(R.drawable.fp_to_unlock) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.locked_fp,
+            context.getDrawable(R.drawable.unlock_to_fp) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenLocked <=> AodLocked
+        animatedIconDrawable.addTransition(
+            R.id.locked_aod,
+            R.id.locked,
+            context.getDrawable(R.drawable.lock_aod_to_ls) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.locked,
+            R.id.locked_aod,
+            context.getDrawable(R.drawable.lock_ls_to_aod) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenUnlocked <=> AodUnlocked
+        animatedIconDrawable.addTransition(
+            R.id.unlocked_aod,
+            R.id.unlocked,
+            context.getDrawable(R.drawable.unlocked_aod_to_ls) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.unlocked_aod,
+            context.getDrawable(R.drawable.unlocked_ls_to_aod) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenLocked <=> LockscreenUnlocked
+        animatedIconDrawable.addTransition(
+            R.id.locked,
+            R.id.unlocked,
+            context.getDrawable(R.drawable.lock_to_unlock) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.locked,
+            context.getDrawable(R.drawable.unlocked_to_locked) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenFingerprint <=> LockscreenLocked
+        animatedIconDrawable.addTransition(
+            R.id.locked_fp,
+            R.id.locked,
+            context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable,
+            /* reversible */ true,
+        )
+
+        // LockscreenUnlocked <=> AodLocked
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.locked_aod,
+            context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable,
+            /* reversible */ true,
+        )
+    }
+
+    private fun addLongpressHandlingView() {
+        addView(longPressHandlingView)
+        val lp = longPressHandlingView.layoutParams as LayoutParams
+        lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+        longPressHandlingView.setLayoutParams(lp)
+    }
+
+    private fun addIconImageView() {
+        iconView.scaleType = ImageView.ScaleType.CENTER_CROP
+        iconView.setImageDrawable(animatedIconDrawable)
+        addView(iconView)
+        val lp = iconView.layoutParams as LayoutParams
+        lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.gravity = Gravity.CENTER
+        iconView.setLayoutParams(lp)
+    }
+
+    private fun addBgImageView() {
+        bgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg))
+        addView(bgView)
+        val lp = bgView.layoutParams as LayoutParams
+        lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+        bgView.setLayoutParams(lp)
+    }
+
+    fun getIconState(icon: IconType, aod: Boolean): IntArray {
+        val lockIconState = IntArray(2)
+        when (icon) {
+            IconType.LOCK -> lockIconState[0] = android.R.attr.state_first
+            IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last
+            IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle
+        }
+        if (aod) {
+            lockIconState[1] = android.R.attr.state_single
+        } else {
+            lockIconState[1] = -android.R.attr.state_single
+        }
+        return lockIconState
+    }
+
+    enum class IconType {
+        LOCK,
+        UNLOCK,
+        FINGERPRINT,
+    }
+
+    enum class AccessibilityHintType {
+        NONE,
+        AUTHENTICATE,
+        ENTER,
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
new file mode 100644
index 0000000..f7ab1ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ProgressBar
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+private const val TAG = "SideFpsProgressBar"
+
+const val progressBarHeight = 100
+
+@SysUISingleton
+class SideFpsProgressBar
+@Inject
+constructor(
+    private val layoutInflater: LayoutInflater,
+    private val windowManager: WindowManager,
+) {
+    private var progressBarWidth = 200
+    fun updateView(
+        visible: Boolean,
+        location: Point,
+        shouldRotate90Degrees: Boolean,
+        progressBarWidth: Int
+    ) {
+        if (visible) {
+            this.progressBarWidth = progressBarWidth
+            createAndShowOverlay(location, shouldRotate90Degrees)
+        } else {
+            hideOverlay()
+        }
+    }
+
+    fun hideOverlay() {
+        overlayView = null
+    }
+
+    private val overlayViewParams =
+        WindowManager.LayoutParams(
+                progressBarHeight,
+                progressBarWidth,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+                PixelFormat.TRANSPARENT
+            )
+            .apply {
+                title = TAG
+                fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+                gravity = Gravity.TOP or Gravity.LEFT
+                layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                privateFlags =
+                    WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+                        WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+            }
+
+    private var overlayView: View? = null
+        set(value) {
+            field?.let { oldView -> windowManager.removeView(oldView) }
+            field = value
+            field?.let { newView -> windowManager.addView(newView, overlayViewParams) }
+        }
+
+    private fun createAndShowOverlay(
+        fingerprintSensorLocation: Point,
+        shouldRotate90Degrees: Boolean
+    ) {
+        if (overlayView == null) {
+            overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+        }
+        overlayViewParams.x = fingerprintSensorLocation.x
+        overlayViewParams.y = fingerprintSensorLocation.y
+        if (shouldRotate90Degrees) {
+            overlayView?.rotation = 270.0f
+            overlayViewParams.width = progressBarHeight
+            overlayViewParams.height = progressBarWidth
+        } else {
+            overlayView?.rotation = 0.0f
+            overlayViewParams.width = progressBarWidth
+            overlayViewParams.height = progressBarHeight
+        }
+        windowManager.updateViewLayout(overlayView, overlayViewParams)
+    }
+
+    fun setProgress(value: Float) {
+        overlayView
+            ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
+            ?.setProgress((value * 100).toInt(), false)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index e8df1a6..21eba56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -17,13 +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.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
@@ -43,7 +44,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultLockIconSection: DefaultLockIconSection,
+    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
     defaultShortcutsSection: DefaultShortcutsSection,
     defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
@@ -53,13 +54,14 @@
     splitShadeGuidelines: SplitShadeGuidelines,
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
+    communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
 ) : KeyguardBlueprint {
     override val id: String = DEFAULT
 
     override val sections =
         setOf(
             defaultIndicationAreaSection,
-            defaultLockIconSection,
+            defaultDeviceEntryIconSection,
             defaultShortcutsSection,
             defaultAmbientIndicationAreaSection,
             defaultSettingsPopupMenuSection,
@@ -69,6 +71,7 @@
             splitShadeGuidelines,
             aodNotificationIconsSection,
             aodBurnInSection,
+            communalTutorialIndicatorSection,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index ce76f56..f8dd7c1 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
@@ -23,8 +23,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
@@ -38,7 +38,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultLockIconSection: DefaultLockIconSection,
+    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
     defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
@@ -54,7 +54,7 @@
     override val sections =
         setOf(
             defaultIndicationAreaSection,
-            defaultLockIconSection,
+            defaultDeviceEntryIconSection,
             defaultAmbientIndicationAreaSection,
             defaultSettingsPopupMenuSection,
             alignShortcutsToUdfpsSection,
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 669db34..b7fe960 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
@@ -26,23 +26,40 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
+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.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
 import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
 
 class AodNotificationIconsSection
 @Inject
 constructor(
     private val context: Context,
-    private val featureFlags: FeatureFlags,
+    private val configurationState: ConfigurationState,
+    private val configurationController: ConfigurationController,
+    private val dozeParameters: DozeParameters,
+    private val featureFlags: FeatureFlagsClassic,
+    private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+    private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationPanelView: NotificationPanelView,
     private val notificationIconAreaController: NotificationIconAreaController,
+    private val screenOffAnimationController: ScreenOffAnimationController,
 ) : KeyguardSection() {
+
+    private var nicBindingDisposable: DisposableHandle? = null
     private val nicId = R.id.aod_notification_icon_container
     private lateinit var nic: NotificationIconContainer
 
@@ -70,7 +87,23 @@
             return
         }
 
-        notificationIconAreaController.setupAodIcons(nic)
+        if (NotificationIconContainerRefactor.isEnabled) {
+            nic.setOnLockScreen(true)
+            nicBindingDisposable?.dispose()
+            nicBindingDisposable =
+                NotificationIconContainerViewBinder.bind(
+                    nic,
+                    nicAodViewModel,
+                    configurationState,
+                    configurationController,
+                    dozeParameters,
+                    featureFlags,
+                    screenOffAnimationController,
+                    nicAodIconViewStore,
+                )
+        } else {
+            notificationIconAreaController.setupAodIcons(nic)
+        }
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -102,5 +135,6 @@
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
         constraintLayout.removeView(nicId)
+        nicBindingDisposable?.dispose()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
index f4bc713..62c5988 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
@@ -33,11 +33,18 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
+import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-class DefaultLockIconSection
+@ExperimentalCoroutinesApi
+class DefaultDeviceEntryIconSection
 @Inject
 constructor(
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -46,24 +53,47 @@
     private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val featureFlags: FeatureFlags,
-    private val lockIconViewController: LockIconViewController,
+    private val lockIconViewController: Lazy<LockIconViewController>,
+    private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+    private val falsingManager: Lazy<FalsingManager>,
 ) : KeyguardSection() {
-    private val lockIconViewId = R.id.lock_icon_view
+    private val deviceEntryIconViewId = R.id.device_entry_icon_view
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+        if (
+            !featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) &&
+                !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)
+        ) {
             return
         }
-        notificationPanelView.findViewById<View>(lockIconViewId).let {
+
+        notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
             notificationPanelView.removeView(it)
         }
-        val view = LockIconView(context, null).apply { id = lockIconViewId }
+
+        val view =
+            if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
+            } else {
+                // Flags.MIGRATE_LOCK_ICON
+                LockIconView(context, null).apply { id = deviceEntryIconViewId }
+            }
         constraintLayout.addView(view)
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        constraintLayout.findViewById<LockIconView?>(lockIconViewId)?.let {
-            lockIconViewController.setLockIconView(it)
+        if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+            constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
+                DeviceEntryIconViewBinder.bind(
+                    it,
+                    deviceEntryIconViewModel.get(),
+                    falsingManager.get(),
+                )
+            }
+        } else {
+            constraintLayout.findViewById<LockIconView?>(deviceEntryIconViewId)?.let {
+                lockIconViewController.get().setLockIconView(it)
+            }
         }
     }
 
@@ -84,30 +114,30 @@
         val defaultDensity =
             DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
                 DisplayMetrics.DENSITY_DEFAULT.toFloat()
-        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+        val iconRadiusPx = (defaultDensity * 36).toInt()
 
         if (isUdfpsSupported) {
             authController.udfpsLocation?.let { udfpsLocation ->
-                centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
+                centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
             }
         } else {
-            centerLockIcon(
+            centerIcon(
                 Point(
                     (widthPixels / 2).toInt(),
-                    (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+                    (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt()
                 ),
-                lockIconRadiusPx * scaleFactor,
+                iconRadiusPx * scaleFactor,
                 constraintSet,
             )
         }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
-        constraintLayout.removeView(lockIconViewId)
+        constraintLayout.removeView(deviceEntryIconViewId)
     }
 
     @VisibleForTesting
-    internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) {
+    internal fun centerIcon(center: Point, radius: Float, constraintSet: ConstraintSet) {
         val sensorRect =
             Rect().apply {
                 set(
@@ -119,17 +149,17 @@
             }
 
         constraintSet.apply {
-            constrainWidth(lockIconViewId, sensorRect.right - sensorRect.left)
-            constrainHeight(lockIconViewId, sensorRect.bottom - sensorRect.top)
+            constrainWidth(deviceEntryIconViewId, sensorRect.right - sensorRect.left)
+            constrainHeight(deviceEntryIconViewId, sensorRect.bottom - sensorRect.top)
             connect(
-                lockIconViewId,
+                deviceEntryIconViewId,
                 ConstraintSet.TOP,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.TOP,
                 sensorRect.top
             )
             connect(
-                lockIconViewId,
+                deviceEntryIconViewId,
                 ConstraintSet.START,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.START,
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
new file mode 100644
index 0000000..023d16ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to
+ * consume.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class AlternateBouncerToGoneTransitionViewModel
+@Inject
+constructor(
+    bouncerToGoneFlows: BouncerToGoneFlows,
+) {
+
+    /** Scrim alpha values */
+    val scrimAlpha: Flow<ScrimAlpha> =
+        bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+}
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
new file mode 100644
index 0000000..024707a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class AodToLockscreenTransitionViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+) {
+
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_LOCKSCREEN_DURATION,
+            transitionFlow = interactor.aodToLockscreenTransition,
+        )
+
+    /** Ensure alpha is set to be visible */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 500.milliseconds,
+            onStart = { 1f },
+            onStep = { 1f },
+        )
+}
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
new file mode 100644
index 0000000..da74f2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+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
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import dagger.Lazy
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/** ALTERNATE and PRIMARY bouncers common animations */
+@OptIn(ExperimentalCoroutinesApi::class)
+class BouncerToGoneFlows
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
+    private val featureFlags: FeatureFlagsClassic,
+    private val shadeInteractor: ShadeInteractor,
+) {
+    /** Common fade for scrim alpha values during *BOUNCER->GONE */
+    fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
+        return if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            keyguardDismissActionInteractor
+                .get()
+                .willAnimateDismissActionOnLockscreen
+                .flatMapLatest { createScrimAlphaFlow(duration, fromState) { it } }
+        } else {
+            createScrimAlphaFlow(
+                duration,
+                fromState,
+                primaryBouncerInteractor::willRunDismissFromKeyguard
+            )
+        }
+    }
+
+    private fun createScrimAlphaFlow(
+        duration: Duration,
+        fromState: KeyguardState,
+        willRunAnimationOnKeyguard: () -> Boolean
+    ): Flow<ScrimAlpha> {
+        var isShadeExpanded = false
+        var leaveShadeOpen: Boolean = false
+        var willRunDismissFromKeyguard: Boolean = false
+        val transitionAnimation =
+            KeyguardTransitionAnimationFlow(
+                transitionDuration = duration,
+                transitionFlow = interactor.transition(fromState, GONE)
+            )
+
+        return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion ->
+            transitionAnimation
+                .createFlow(
+                    duration = duration,
+                    interpolator = EMPHASIZED_ACCELERATE,
+                    onStart = {
+                        leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                        willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
+                        isShadeExpanded = shadeExpansion > 0f
+                    },
+                    onStep = { 1f - it },
+                )
+                .map {
+                    if (willRunDismissFromKeyguard) {
+                        if (isShadeExpanded) {
+                            ScrimAlpha(
+                                behindAlpha = it,
+                                notificationsAlpha = it,
+                            )
+                        } else {
+                            ScrimAlpha()
+                        }
+                    } else if (leaveShadeOpen) {
+                        ScrimAlpha(
+                            behindAlpha = 1f,
+                            notificationsAlpha = 1f,
+                        )
+                    } else {
+                        ScrimAlpha(behindAlpha = it)
+                    }
+                }
+        }
+    }
+}
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
new file mode 100644
index 0000000..842dde3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@ExperimentalCoroutinesApi
+class DeviceEntryIconViewModel @Inject constructor() {
+    // TODO: b/305234447 update these states from the data layer
+    val iconViewModel: Flow<IconViewModel> =
+        flowOf(
+            IconViewModel(
+                type = DeviceEntryIconView.IconType.LOCK,
+                useAodVariant = false,
+                tint = Color.WHITE,
+                alpha = 1f,
+                padding = 48,
+            )
+        )
+    val backgroundViewModel: Flow<BackgroundViewModel> =
+        flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY))
+    val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f))
+    val isLongPressEnabled: Flow<Boolean> = flowOf(true)
+    val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+        flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
+
+    fun onLongPress() {
+        // TODO() vibrate & perform action based on current lock/unlock state
+    }
+    data class BurnInViewModel(
+        val x: Int, // current x burn in offset based on the aodTransitionAmount
+        val y: Int, // current y burn in offset based on the aodTransitionAmount
+        val progress: Float, // current progress based on the aodTransitionAmount
+    )
+
+    class IconViewModel(
+        val type: DeviceEntryIconView.IconType,
+        val useAodVariant: Boolean,
+        val tint: Int,
+        val alpha: Float,
+        val padding: Int,
+    )
+
+    class BackgroundViewModel(
+        val alpha: Float,
+        val tint: Int,
+    )
+}
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
new file mode 100644
index 0000000..601dbcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.dagger.SysUISingleton
+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.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class GoneToAodTransitionViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+) {
+
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_AOD_DURATION,
+            transitionFlow = interactor.goneToAodTransition,
+        )
+
+    /** y-translation from the top of the screen for AOD */
+    fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
+        return transitionAnimation.createFlow(
+            startTime = 600.milliseconds,
+            duration = 500.milliseconds,
+            onStart = { translatePx },
+            onStep = { translatePx + it * -translatePx },
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_DECELERATE,
+        )
+    }
+
+    /** alpha animation upon entering AOD */
+    val enterFromTopAnimationAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            startTime = 600.milliseconds,
+            duration = 500.milliseconds,
+            onStart = { 0f },
+            onStep = { it },
+        )
+}
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 89835fe..1f98082 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,14 +17,19 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.Context
 import android.util.MathUtils
+import android.view.View.VISIBLE
 import com.android.app.animation.Interpolators
 import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,17 +37,23 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardRootViewModel
 @Inject
 constructor(
+    private val context: Context,
     private val keyguardInteractor: KeyguardInteractor,
     private val burnInInteractor: BurnInInteractor,
+    private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+    private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
 
     data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -56,9 +67,12 @@
 
     public var clockControllerProvider: Provider<ClockController>? = null
 
-    /** Represents the current state of the KeyguardRootView visibility */
-    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
-        keyguardInteractor.keyguardRootViewVisibilityState
+    val burnInLayerVisibility: Flow<Int> =
+        keyguardTransitionInteractor.startedKeyguardState
+            .filter { it == AOD || it == LOCKSCREEN }
+            .map { VISIBLE }
+
+    val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition
 
     /** An observable for the alpha level for the entire keyguard root view. */
     val alpha: Flow<Float> =
@@ -70,9 +84,14 @@
             }
         }
 
-    private val burnIn: Flow<BurnInModel> =
-        combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn
-            ->
+    private fun burnIn(): Flow<BurnInModel> {
+        val dozingAmount: Flow<Float> =
+            merge(
+                keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+                keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+            )
+
+        return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
             val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
             val useScaleOnly =
                 clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false
@@ -91,13 +110,61 @@
                 )
             }
         }
+    }
+
+    /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
+    val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
+
+    /** For elements that appear and move during the animation -> AOD */
+    val burnInLayerAlpha: Flow<Float> =
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(1f)
+            } else {
+                goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+            }
+        }
 
     val translationY: Flow<Float> =
-        merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() })
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(0f)
+            } else {
+                keyguardInteractor.configurationChange.flatMapLatest { _ ->
+                    val enterFromTopAmount =
+                        context.resources.getDimensionPixelSize(
+                            R.dimen.keyguard_enter_from_top_translation_y
+                        )
+                    combine(
+                        keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+                        burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
+                        goneToAodTransitionViewModel
+                            .enterFromTopTranslationY(enterFromTopAmount)
+                            .onStart { emit(0f) },
+                    ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY ->
+                        // All 3 values need to be combined for a smooth translation
+                        keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY
+                    }
+                }
+            }
+        }
 
-    val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() }
+    val translationX: Flow<Float> =
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(0f)
+            } else {
+                burnIn().map { it.translationX.toFloat() }
+            }
+        }
 
-    val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) }
+    val scale: Flow<Pair<Float, Boolean>> =
+        previewMode.flatMapLatest { previewMode ->
+            burnIn().map {
+                val scale = if (previewMode.isInPreviewMode) 1f else it.scale
+                Pair(scale, it.scaleClockOnly)
+            }
+        }
 
     /**
      * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
@@ -105,11 +172,14 @@
      * lock screen.
      */
     fun enablePreviewMode() {
-        val newPreviewMode = PreviewMode(true)
-        previewMode.value = newPreviewMode
+        previewMode.value = PreviewMode(true)
     }
 
     fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) {
+        // Notifications should not be visible in preview mode
+        if (previewMode.value.isInPreviewMode) {
+            return
+        }
         keyguardInteractor.sharedNotificationContainerPosition.value =
             SharedNotificationContainerPosition(top, bottom)
     }
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 0783181..0e95be2 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
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -24,6 +23,8 @@
 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
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -33,7 +34,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -49,11 +49,12 @@
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
     featureFlags: FeatureFlagsClassic,
+    bouncerToGoneFlows: BouncerToGoneFlows,
 ) {
     private val transitionAnimation =
         KeyguardTransitionAnimationFlow(
             transitionDuration = TO_GONE_DURATION,
-            transitionFlow = interactor.primaryBouncerToGoneTransition,
+            transitionFlow = interactor.transition(PRIMARY_BOUNCER, GONE)
         )
 
     private var leaveShadeOpen: Boolean = false
@@ -110,38 +111,6 @@
         )
     }
 
-    /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
-        if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
-            keyguardDismissActionInteractor
-                .get()
-                .willAnimateDismissActionOnLockscreen
-                .flatMapLatest { createScrimAlphaFlow { it } }
-        } else {
-            createScrimAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
-        }
-    private fun createScrimAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<ScrimAlpha> {
-        return transitionAnimation
-            .createFlow(
-                duration = TO_GONE_DURATION,
-                interpolator = EMPHASIZED_ACCELERATE,
-                onStart = {
-                    leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
-                    willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
-                },
-                onStep = { 1f - it },
-            )
-            .map {
-                if (willRunDismissFromKeyguard) {
-                    ScrimAlpha()
-                } else if (leaveShadeOpen) {
-                    ScrimAlpha(
-                        behindAlpha = 1f,
-                        notificationsAlpha = 1f,
-                    )
-                } else {
-                    ScrimAlpha(behindAlpha = it)
-                }
-            }
-    }
+        bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER)
 }
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
new file mode 100644
index 0000000..2c3b431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.animation.ValueAnimator
+import android.graphics.Point
+import androidx.core.animation.doOnEnd
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+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 javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewModel
+@Inject
+constructor(
+    private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val sfpsSensorInteractor: SideFpsSensorInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    private val _progress = MutableStateFlow(0.0f)
+    private val _visible = MutableStateFlow(false)
+    private var _animator: ValueAnimator? = null
+
+    private fun onFingerprintCaptureCompleted() {
+        _visible.value = false
+        _progress.value = 0.0f
+    }
+
+    val isVisible: Flow<Boolean> = _visible.asStateFlow()
+
+    val progress: Flow<Float> = _progress.asStateFlow()
+
+    val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+
+    val sensorLocation: Flow<Point> =
+        sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+
+    val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+
+    val shouldRotate90Degrees: Flow<Boolean> =
+        combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+            .map { (rotation, sensorLocation) ->
+                if (rotation.isDefaultOrientation()) {
+                    sensorLocation.isSensorVerticalInDefaultOrientation
+                } else {
+                    !sensorLocation.isSensorVerticalInDefaultOrientation
+                }
+            }
+
+    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+        sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
+
+    init {
+        applicationScope.launch {
+            combine(
+                    sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication,
+                    sfpsSensorInteractor.authenticationDuration,
+                    ::Pair
+                )
+                .collectLatest { (enabled, authDuration) ->
+                    if (!enabled) return@collectLatest
+
+                    launch {
+                        fpAuthRepository.authenticationStatus.collectLatest { authStatus ->
+                            when (authStatus) {
+                                is AcquiredFingerprintAuthenticationStatus -> {
+                                    if (authStatus.fingerprintCaptureStarted) {
+
+                                        _visible.value = true
+                                        _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
+                                                            }
+                                                        }
+                                                    )
+                                                }
+                                        _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
+                            }
+                        }
+                    }
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
index 929f27f..dca151d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -17,20 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.graphics.Rect
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
-class UdfpsKeyguardViewModel
-@Inject
-constructor(
-    private val featureFlags: FeatureFlags,
-) {
+class UdfpsKeyguardViewModel @Inject constructor() {
     var sensorBounds: Rect = Rect()
-
-    fun useExpandedOverlay(): Boolean {
-        return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
index 6575cdd..c73afe8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
@@ -13,16 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.log.dagger
 
 import javax.inject.Qualifier
 
-/**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
- */
+/** A [com.android.systemui.log.LogBuffer] for Bluetooth QS tile dialog related logging. */
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
+annotation class BluetoothTileDialogLog
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 67531ad..e768f16 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -91,7 +91,7 @@
     @SysUISingleton
     @NotifInflationLog
     public static LogBuffer provideNotifInflationLogBuffer(LogBufferFactory factory) {
-        return factory.create("NotifInflationLog", 100);
+        return factory.create("NotifInflationLog", 250);
     }
 
     /** Provides a logging buffer for notification interruption calculations. */
@@ -190,17 +190,6 @@
         }
     }
 
-    /**
-     * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
-     * unique for each tile.
-     * go/qs-tile-refactor
-     */
-    @Provides
-    @QSTilesDefaultLog
-    public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
-        return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
-    }
-
     @Provides
     @QSTilesLogBuffers
     public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
@@ -563,4 +552,12 @@
     public static LogBuffer provideSceneFrameworkLogBuffer(LogBufferFactory factory) {
         return factory.create("SceneFramework", 50);
     }
+
+    /** Provides a {@link LogBuffer} for the bluetooth QS tile dialog. */
+    @Provides
+    @SysUISingleton
+    @BluetoothTileDialogLog
+    public static LogBuffer provideQBluetoothTileDialogLogBuffer(LogBufferFactory factory) {
+        return factory.create("BluetoothTileDialogLog", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 565bf24..baa07c1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -95,7 +95,7 @@
         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
         initialValue
     }
-    return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
+    return this.pairwiseBy(initialValueFun) { prevVal: Boolean, newVal: Boolean ->
         if (prevVal != newVal) {
             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
         }
@@ -114,7 +114,7 @@
         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
         initialValue
     }
-    return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int ->
+    return this.pairwiseBy(initialValueFun) { prevVal: Int, newVal: Int ->
         if (prevVal != newVal) {
             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
         }
@@ -133,7 +133,7 @@
         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
         initialValue
     }
-    return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+    return this.pairwiseBy(initialValueFun) { prevVal: Int?, newVal: Int? ->
         if (prevVal != newVal) {
             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
         }
@@ -152,7 +152,7 @@
         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
         initialValue
     }
-    return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? ->
+    return this.pairwiseBy(initialValueFun) { prevVal: String?, newVal: String? ->
         if (prevVal != newVal) {
             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
         }
@@ -176,7 +176,7 @@
         )
         initialValue
     }
-    return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+    return this.pairwiseBy(initialValueFun) { prevVal: List<T>, newVal: List<T> ->
         if (prevVal != newVal) {
             // TODO(b/267761156): Can we log list changes without using toString?
             tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 3d4fca1..1b3b473 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -51,11 +51,12 @@
         Uri uri;
         boolean looping;
         AudioAttributes attributes;
+        float volume;
         long requestTime;
 
         public String toString() {
             return "{ code=" + code + " looping=" + looping + " attributes=" + attributes
-                    + " uri=" + uri + " }";
+                    + " volume=" + volume + " uri=" + uri + " }";
         }
     }
 
@@ -101,6 +102,7 @@
                     player.setAudioAttributes(mCmd.attributes);
                     player.setDataSource(mCmd.context, mCmd.uri);
                     player.setLooping(mCmd.looping);
+                    player.setVolume(mCmd.volume);
                     player.setOnCompletionListener(NotificationPlayer.this);
                     player.setOnErrorListener(NotificationPlayer.this);
                     player.prepare();
@@ -401,10 +403,11 @@
      *          (see {@link MediaPlayer#setLooping(boolean)})
      * @param stream the AudioStream to use.
      *          (see {@link MediaPlayer#setAudioStreamType(int)})
+     * @param volume the volume for the audio with values in range [0.0, 1.0]
      * @deprecated use {@link #play(Context, Uri, boolean, AudioAttributes)} instead.
      */
     @Deprecated
-    public void play(Context context, Uri uri, boolean looping, int stream) {
+    public void play(Context context, Uri uri, boolean looping, int stream, float volume) {
         if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
         Command cmd = new Command();
@@ -414,6 +417,7 @@
         cmd.uri = uri;
         cmd.looping = looping;
         cmd.attributes = new AudioAttributes.Builder().setInternalLegacyStreamType(stream).build();
+        cmd.volume = volume;
         synchronized (mCmdQueue) {
             enqueueLocked(cmd);
             mState = PLAY;
@@ -432,8 +436,10 @@
      *          (see {@link MediaPlayer#setLooping(boolean)})
      * @param attributes the AudioAttributes to use.
      *          (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
+     * @param volume the volume for the audio with values in range [0.0, 1.0]
      */
-    public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+    public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes,
+            float volume) {
         if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
@@ -442,6 +448,7 @@
         cmd.uri = uri;
         cmd.looping = looping;
         cmd.attributes = attributes;
+        cmd.volume = volume;
         synchronized (mCmdQueue) {
             enqueueLocked(cmd);
             mState = PLAY;
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 80be766..7a48836 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -285,7 +285,8 @@
         }
 
         @Override
-        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
+        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa,
+                float volume) {
             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                 throw new SecurityException("Async playback only available from system UID.");
@@ -293,7 +294,7 @@
             if (UserHandle.ALL.equals(user)) {
                 user = UserHandle.SYSTEM;
             }
-            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
+            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa, volume);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index a1291a4..724241d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.SystemProperties
 import android.util.Log
+import com.android.internal.annotations.KeepForWeakReference
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.qualifiers.Main
@@ -82,6 +83,8 @@
     private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     private var reactivatedKey: String? = null
 
+    // Ensure the field (and associated reference) isn't removed during optimization.
+    @KeepForWeakReference
     private val userTrackerCallback =
         object : UserTracker.Callback {
             override fun onUserChanged(newUser: Int, userContext: Context) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 0f3e0ac..2a32ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -60,7 +60,6 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 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
@@ -83,6 +82,7 @@
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
@@ -90,6 +90,7 @@
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.ThreadFactory
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.traceSection
 import java.io.IOException
@@ -245,7 +246,7 @@
     @Inject
     constructor(
         context: Context,
-        @Background backgroundExecutor: Executor,
+        threadFactory: ThreadFactory,
         @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
         mediaControllerFactory: MediaControllerFactory,
@@ -267,7 +268,9 @@
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
     ) : this(
         context,
-        backgroundExecutor,
+        // Loading bitmap for UMO background can take longer time, so it cannot run on the default
+        // background thread. Use a custom thread for media.
+        threadFactory.buildExecutorOnNewThread(TAG),
         uiExecutor,
         foregroundExecutor,
         mediaControllerFactory,
@@ -1429,8 +1432,6 @@
     }
 
     private fun onSessionDestroyed(key: String) {
-        if (!mediaFlags.isRetainingPlayersEnabled()) return
-
         if (DEBUG) Log.d(TAG, "session destroyed for $key")
         val entry = mediaEntries.remove(key) ?: return
         // Clear token since the session is no longer valid
@@ -1474,7 +1475,7 @@
             if (DEBUG) Log.d(TAG, "Removing still-active player $key")
             notifyMediaDataRemoved(key)
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
-        } else {
+        } else if (mediaFlags.isRetainingPlayersEnabled() || isAbleToResume(removed)) {
             // Convert to resume
             if (DEBUG) {
                 Log.d(
@@ -1484,6 +1485,11 @@
                 )
             }
             convertToResumePlayer(key, removed)
+        } else {
+            // Retaining players flag is off and app doesn't support resume: remove player.
+            if (DEBUG) Log.d(TAG, "Removing player $key")
+            notifyMediaDataRemoved(key)
+            logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 1fe93ed..2034d97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
 import android.media.session.MediaController
 import android.text.TextUtils
 import android.util.Log
@@ -31,18 +32,22 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Lazy
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -58,13 +63,14 @@
     private val context: Context,
     private val controllerFactory: MediaControllerFactory,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
-    private val mr2manager: MediaRouter2Manager,
+    private val mr2manager: Lazy<MediaRouter2Manager>,
     private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
     private val configurationController: ConfigurationController,
-    private val localBluetoothManager: LocalBluetoothManager?,
+    private val localBluetoothManager: Lazy<LocalBluetoothManager?>,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    private val featureFlags: FeatureFlagsClassic,
 ) : MediaDataManager.Listener, Dumpable {
 
     private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -205,8 +211,8 @@
 
         fun dump(pw: PrintWriter) {
             val routingSession =
-                controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
-            val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) }
+                controller?.let { mr2manager.get().getRoutingSessionForMediaController(it) }
+            val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) }
             with(pw) {
                 println("    current device is ${current?.name}")
                 val type = controller?.playbackInfo?.playbackType
@@ -215,6 +221,7 @@
                 println("    volumeControlId=$volumeControlId cached= $playbackVolumeControlId")
                 println("    routingSession=$routingSession")
                 println("    selectedRoutes=$selectedRoutes")
+                println("    currentConnectedDevice=${localMediaManager.currentConnectedDevice}")
             }
         }
 
@@ -348,16 +355,16 @@
                 }
                 val device =
                     aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
-                val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
+                val routingSession =
+                    controller?.let { mr2manager.get().getRoutingSessionForMediaController(it) }
 
                 // If we have a controller but get a null route, then don't trust the device
-                val enabled = device != null && (controller == null || route != null)
-                val name =
-                    if (controller == null || route != null) {
-                        route?.name?.toString() ?: device?.name
-                    } else {
-                        null
-                    }
+                val enabled = device != null && (controller == null || routingSession != null)
+
+                val name = getDeviceName(device, routingSession)
+                if (DEBUG) {
+                    Log.d(TAG, "new device name $name")
+                }
                 current =
                     MediaDeviceData(
                         enabled,
@@ -369,7 +376,60 @@
             }
         }
 
+        /** Return a display name for the current device / route, or null if not possible */
+        private fun getDeviceName(
+            device: MediaDevice?,
+            routingSession: RoutingSessionInfo?,
+        ): String? {
+            val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) }
+
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "device is $device, controller $controller," +
+                        " routingSession ${routingSession?.name}" +
+                        " or ${selectedRoutes?.firstOrNull()?.name}"
+                )
+            }
+
+            if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) {
+                if (controller == null || routingSession != null) {
+                    return routingSession?.name?.toString() ?: device?.name
+                }
+                return null
+            }
+
+            if (controller == null) {
+                // In resume state, we don't have a controller - just use the device name
+                return device?.name
+            }
+
+            if (routingSession == null) {
+                // This happens when casting from apps that do not support MediaRouter2
+                // The output switcher can't show anything useful here, so set to null
+                return null
+            }
+
+            // If this is a user route (app / cast provided), use the provided name
+            if (!routingSession.isSystemSession) {
+                return routingSession.name?.toString() ?: device?.name
+            }
+
+            selectedRoutes?.firstOrNull()?.let {
+                if (device is PhoneMediaDevice) {
+                    // Get the (localized) name for this phone device
+                    return PhoneMediaDevice.getSystemRouteNameFromType(context, it)
+                } else {
+                    // If it's another type of device (in practice, Bluetooth), use the route name
+                    return it.name.toString()
+                }
+            }
+            return null
+        }
+
+        @WorkerThread
         private fun isLeAudioBroadcastEnabled(): Boolean {
+            val localBluetoothManager = localBluetoothManager.get()
             if (localBluetoothManager != null) {
                 val profileManager = localBluetoothManager.profileManager
                 if (profileManager != null) {
@@ -389,19 +449,20 @@
             return false
         }
 
+        @WorkerThread
         private fun getBroadcastingInfo(bluetoothLeBroadcast: LocalBluetoothLeBroadcast) {
-            var currentBroadcastedApp = bluetoothLeBroadcast.appSourceName
+            val currentBroadcastedApp = bluetoothLeBroadcast.appSourceName
             // TODO(b/233698402): Use the package name instead of app label to avoid the
             // unexpected result.
             // Check the current media app's name is the same with current broadcast app's name
             // or not.
-            var mediaApp =
+            val mediaApp =
                 MediaDataUtils.getAppLabel(
                     context,
                     localMediaManager.packageName,
                     context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name)
                 )
-            var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
+            val isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
             if (isCurrentBroadcastedApp) {
                 broadcastDescription =
                     context.getString(R.string.broadcasting_description_is_broadcasting)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a48e56a..7cb5b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -731,6 +731,7 @@
                     removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
                 removedPlayer?.run {
                     debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+                    onDestroy()
                 }
             }
 
@@ -1302,6 +1303,7 @@
         val removedPlayer = removeMediaPlayer(key)
         if (removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
+            removedPlayer.onDestroy()
         }
         val sortKey =
             MediaSortKey(
@@ -1329,6 +1331,7 @@
         val removedPlayer = removeMediaPlayer(key)
         if (!update && removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
+            removedPlayer.onDestroy()
         }
         val sortKey =
             MediaSortKey(
@@ -1357,7 +1360,10 @@
             // MediaPlayer should not be visible
             // no need to set isDismissed flag.
             val removedPlayer = removeMediaPlayer(newKey)
-            removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+            removedPlayer?.run {
+                debugLogger?.logPotentialMemoryLeak(newKey)
+                onDestroy()
+            }
             mediaData.put(newKey, it)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
new file mode 100644
index 0000000..98a3896
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.os.RemoteException
+import android.util.Log
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Helper class for requesting that the server emit logs describing the MediaProjection setup
+ * experience.
+ */
+@SysUISingleton
+class MediaProjectionMetricsLogger
+@Inject
+constructor(private val service: IMediaProjectionManager) {
+
+    /**
+     * Request to log that the permission was requested.
+     *
+     * @param hostUid The UID of the package that initiates MediaProjection.
+     * @param sessionCreationSource The entry point requesting permission to capture.
+     */
+    fun notifyProjectionInitiated(hostUid: Int, sessionCreationSource: SessionCreationSource) {
+        try {
+            service.notifyPermissionRequestInitiated(
+                hostUid,
+                sessionCreationSource.toMetricsConstant()
+            )
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Error notifying server of projection initiated", e)
+        }
+    }
+
+    /**
+     * Request to log that the permission request was displayed.
+     *
+     * @param hostUid The UID of the package that initiates MediaProjection.
+     */
+    fun notifyPermissionRequestDisplayed(hostUid: Int) {
+        try {
+            service.notifyPermissionRequestDisplayed(hostUid)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Error notifying server of projection displayed", e)
+        }
+    }
+
+    /**
+     * Request to log that the permission request was cancelled.
+     *
+     * @param hostUid The UID of the package that initiates MediaProjection.
+     */
+    fun notifyProjectionRequestCancelled(hostUid: Int) {
+        try {
+            service.notifyPermissionRequestCancelled(hostUid)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Error notifying server of projection cancelled", e)
+        }
+    }
+
+    /**
+     * Request to log that the app selector was displayed.
+     *
+     * @param hostUid The UID of the package that initiates MediaProjection.
+     */
+    fun notifyAppSelectorDisplayed(hostUid: Int) {
+        try {
+            service.notifyAppSelectorDisplayed(hostUid)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Error notifying server of app selector displayed", e)
+        }
+    }
+
+    companion object {
+        const val TAG = "MediaProjectionMetricsLogger"
+    }
+}
+
+enum class SessionCreationSource {
+    APP,
+    CAST,
+    SYSTEM_UI_SCREEN_RECORDER,
+    UNKNOWN;
+
+    fun toMetricsConstant(): Int =
+        when (this) {
+            APP -> METRICS_CREATION_SOURCE_APP
+            CAST -> METRICS_CREATION_SOURCE_CAST
+            SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+            UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN
+        }
+}
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 b5d3e91..50e9e751 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -87,14 +87,16 @@
 
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
-    public override fun onCreate(bundle: Bundle?) {
+    public override fun onCreate(savedInstanceState: Bundle?) {
         lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
         component =
             componentFactory.create(
                 hostUserHandle = hostUserHandle,
+                hostUid = hostUid,
                 callingPackage = callingPackage,
                 view = this,
-                resultHandler = this
+                resultHandler = this,
+                isFirstStart = savedInstanceState == null
             )
         component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
@@ -113,7 +115,7 @@
         reviewGrantedConsentRequired =
             intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
 
-        super.onCreate(bundle)
+        super.onCreate(savedInstanceState)
         controller.init()
         // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
         // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
@@ -208,6 +210,10 @@
                 reviewGrantedConsentRequired,
                 /* projection= */ null
             )
+            if (isFinishing) {
+                // Only log dismissed when actually finishing, and not when changing configuration.
+                controller.onSelectorDismissed()
+            }
         }
         activityLauncher.destroy()
         controller.destroy()
@@ -304,6 +310,17 @@
                 )
         }
 
+    private val hostUid: Int
+        get() {
+            if (!intent.hasExtra(EXTRA_HOST_APP_UID)) {
+                error(
+                    "MediaProjectionAppSelectorActivity should be provided with " +
+                        "$EXTRA_HOST_APP_UID extra"
+                )
+            }
+            return intent.getIntExtra(EXTRA_HOST_APP_UID, /* defaultValue= */ -1)
+        }
+
     companion object {
         const val TAG = "MediaProjectionAppSelectorActivity"
 
@@ -314,8 +331,16 @@
          */
         const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
 
-        /** UID of the app that originally launched the media projection flow (host app user) */
+        /**
+         * User on the device that launched the media projection flow. (Primary, Secondary, Guest,
+         * Work, etc)
+         */
         const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
+        /**
+         * The kernel user-ID that has been assigned to the app that originally launched the media
+         * projection flow.
+         */
+        const val EXTRA_HOST_APP_UID = "launched_from_host_uid"
         const val KEY_CAPTURE_TARGET = "capture_region"
 
         /** Set up intent for the [ChooserActivity] */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 72aea04..d247122 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProvider
+import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProviderImpl
 import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
 import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
 import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity
@@ -55,6 +57,8 @@
 
 @Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
 
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUid
+
 @Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
 
 @Module(
@@ -106,6 +110,8 @@
         impl: TaskPreviewSizeProvider
     ): DefaultLifecycleObserver
 
+    @Binds fun windowMetricsProvider(impl: WindowMetricsProviderImpl): WindowMetricsProvider
+
     companion object {
         @Provides
         @MediaProjectionAppSelector
@@ -139,9 +145,13 @@
         /** Create a factory to inject the activity into the graph */
         fun create(
             @BindsInstance @HostUserHandle hostUserHandle: UserHandle,
+            @BindsInstance @HostUid hostUid: Int,
             @BindsInstance @MediaProjectionAppSelector callingPackage: String?,
             @BindsInstance view: MediaProjectionAppSelectorView,
             @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+            // Whether the app selector is starting for the first time. False when it is re-starting
+            // due to a config change.
+            @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,
         ): MediaProjectionAppSelectorComponent
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index e61650f..575e198 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -18,12 +18,18 @@
 
 import android.content.ComponentName
 import android.os.UserHandle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.shared.recents.model.ThumbnailData
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 @MediaProjectionAppSelectorScope
@@ -36,16 +42,31 @@
     @HostUserHandle private val hostUserHandle: UserHandle,
     @MediaProjectionAppSelector private val scope: CoroutineScope,
     @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
-    @MediaProjectionAppSelector private val callerPackageName: String?
+    @MediaProjectionAppSelector private val callerPackageName: String?,
+    private val thumbnailLoader: RecentTaskThumbnailLoader,
+    @MediaProjectionAppSelector private val isFirstStart: Boolean,
+    private val logger: MediaProjectionMetricsLogger,
+    @HostUid private val hostUid: Int,
 ) {
 
     fun init() {
+        // Only log during the first start of the app selector.
+        // Don't log when the app selector restarts due to a config change.
+        if (isFirstStart) {
+            logger.notifyAppSelectorDisplayed(hostUid)
+        }
+
         scope.launch {
             val recentTasks = recentTaskListProvider.loadRecentTasks()
 
             val tasks =
                 recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks()
 
+            // Thumbnails are not fresh for the foreground task(s). They are only refreshed at
+            // launch, going to home, or going to overview.
+            // For this reason, we need to refresh them here.
+            refreshForegroundTaskThumbnails(tasks)
+
             view.bind(tasks)
         }
     }
@@ -54,6 +75,20 @@
         scope.cancel()
     }
 
+    fun onSelectorDismissed() {
+        logger.notifyProjectionRequestCancelled(hostUid)
+    }
+
+    private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) {
+        coroutineScope {
+            val thumbnails: List<Deferred<ThumbnailData?>> =
+                tasks
+                    .filter { it.isForegroundTask }
+                    .map { async { thumbnailLoader.captureThumbnail(it.taskId) } }
+            thumbnails.forEach { thumbnail -> thumbnail.await() }
+        }
+    }
+
     /** Removes all recent tasks that should be blocked according to the policy */
     private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = filter {
         devicePolicyResolver.isScreenCaptureAllowed(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 41e2286..e9b4582 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -22,8 +22,10 @@
 
 data class RecentTask(
     val taskId: Int,
+    val displayId: Int,
     @UserIdInt val userId: Int,
     val topActivityComponent: ComponentName?,
     val baseIntentComponent: ComponentName?,
-    @ColorInt val colorBackground: Int?
+    @ColorInt val colorBackground: Int?,
+    val isForegroundTask: Boolean,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01398cf..730aa62 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -48,17 +48,24 @@
 
     override suspend fun loadRecentTasks(): List<RecentTask> =
         withContext(coroutineDispatcher) {
-            val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
-
-            rawRecentTasks
+            val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+            // Note: the returned task list is from the most-recent to least-recent order.
+            // The last foreground task is at index 1, because at index 0 will be our app selector.
+            val foregroundGroup = groupedTasks.elementAtOrNull(1)
+            val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId
+            val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId
+            val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2)
+            groupedTasks
                 .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
                 .map {
                     RecentTask(
                         it.taskId,
+                        it.displayId,
                         it.userId,
                         it.topActivity,
                         it.baseIntent?.component,
-                        it.taskDescription?.backgroundColor
+                        it.taskDescription?.backgroundColor,
+                        isForegroundTask = it.taskId in foregroundTaskIds
                     )
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
index 47faaed..ccf272c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
@@ -25,6 +25,8 @@
 
 interface RecentTaskThumbnailLoader {
     suspend fun loadThumbnail(taskId: Int): ThumbnailData?
+
+    suspend fun captureThumbnail(taskId: Int): ThumbnailData?
 }
 
 class ActivityTaskManagerThumbnailLoader
@@ -36,8 +38,13 @@
 
     override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
         withContext(coroutineDispatcher) {
-            val thumbnailData =
-                activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
-            if (thumbnailData.thumbnail == null) null else thumbnailData
+            activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false).takeIf {
+                it.thumbnail != null
+            }
+        }
+
+    override suspend fun captureThumbnail(taskId: Int): ThumbnailData? =
+        withContext(coroutineDispatcher) {
+            activityManager.takeTaskThumbnail(taskId).takeIf { it.thumbnail != null }
         }
 }
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 fd1a683..ba837db 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
@@ -130,10 +130,10 @@
                 view.width,
                 view.height
             )
-        activityOptions.setPendingIntentBackgroundActivityStartMode(
+        activityOptions.pendingIntentBackgroundActivityStartMode =
             MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-        )
         activityOptions.launchCookie = launchCookie
+        activityOptions.launchDisplayId = task.displayId
 
         activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
         resultHandler.returnSelectedApp(launchCookie)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 864d35a..c829471 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -19,8 +19,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
-import android.view.WindowInsets.Type
-import android.view.WindowManager
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
@@ -36,7 +34,7 @@
 @Inject
 constructor(
     private val context: Context,
-    private val windowManager: WindowManager,
+    private val windowMetricsProvider: WindowMetricsProvider,
     private val configurationController: ConfigurationController,
 ) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver {
 
@@ -62,17 +60,14 @@
     }
 
     private fun calculateSize(): Rect {
-        val windowMetrics = windowManager.maximumWindowMetrics
-        val maximumWindowHeight = windowMetrics.bounds.height()
-        val width = windowMetrics.bounds.width()
+        val maxWindowBounds = windowMetricsProvider.maximumWindowBounds
+        val maximumWindowHeight = maxWindowBounds.height()
+        val width = maxWindowBounds.width()
         var height = maximumWindowHeight
 
         val isLargeScreen = isLargeScreen(context)
         if (isLargeScreen) {
-            val taskbarSize =
-                windowManager.currentWindowMetrics.windowInsets
-                    .getInsets(Type.tappableElement())
-                    .bottom
+            val taskbarSize = windowMetricsProvider.currentWindowInsets.bottom
             height -= taskbarSize
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt
new file mode 100644
index 0000000..1932920
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt
@@ -0,0 +1,30 @@
+package com.android.systemui.mediaprojection.appselector.view
+
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.WindowInsets
+import android.view.WindowManager
+import javax.inject.Inject
+
+/** Provides values related to window metrics. */
+interface WindowMetricsProvider {
+
+    val maximumWindowBounds: Rect
+
+    val currentWindowInsets: Insets
+}
+
+class WindowMetricsProviderImpl
+@Inject
+constructor(
+    private val windowManager: WindowManager,
+) : WindowMetricsProvider {
+    override val maximumWindowBounds: Rect
+        get() = windowManager.maximumWindowMetrics.bounds
+
+    override val currentWindowInsets: Insets
+        get() =
+            windowManager.currentWindowMetrics.windowInsets.getInsets(
+                WindowInsets.Type.tappableElement()
+            )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
index 8b437c3..eea369f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
@@ -32,6 +32,7 @@
 import androidx.annotation.DrawableRes
 import androidx.annotation.LayoutRes
 import androidx.annotation.StringRes
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 
@@ -40,16 +41,31 @@
     context: Context,
     private val screenShareOptions: List<ScreenShareOption>,
     private val appName: String?,
+    private val hostUid: Int,
+    private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
     @DrawableRes private val dialogIconDrawable: Int? = null,
-    @ColorRes private val dialogIconTint: Int? = null
+    @ColorRes private val dialogIconTint: Int? = null,
 ) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
     private lateinit var dialogTitle: TextView
     private lateinit var startButton: TextView
     private lateinit var cancelButton: TextView
     private lateinit var warning: TextView
     private lateinit var screenShareModeSpinner: Spinner
+    private var hasCancelBeenLogged: Boolean = false
     var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
 
+    override fun dismiss() {
+        super.dismiss()
+
+        // Dismiss can be called multiple times and we only want to log once.
+        if (hasCancelBeenLogged) {
+            return
+        }
+
+        mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
+        hasCancelBeenLogged = true
+    }
+
     public override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
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 d08d040..eacfa57 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,7 +53,9 @@
 
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
@@ -74,6 +76,7 @@
     private final FeatureFlags mFeatureFlags;
     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
     private final StatusBarManager mStatusBarManager;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     private String mPackageName;
     private int mUid;
@@ -90,15 +93,17 @@
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
             Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
-            StatusBarManager statusBarManager) {
+            StatusBarManager statusBarManager,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
         mFeatureFlags = featureFlags;
         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
         mStatusBarManager = statusBarManager;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
     }
 
     @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
         final Intent launchingIntent = getIntent();
         mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
@@ -133,6 +138,10 @@
 
         try {
             if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+                if (savedInstanceState == null) {
+                    mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                            mUid, SessionCreationSource.APP);
+                }
                 final IMediaProjection projection =
                         MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
                                 mReviewGrantedConsentRequired);
@@ -213,13 +222,19 @@
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
-            mDialog = new MediaProjectionPermissionDialog(dialogContext, getMediaProjectionConfig(),
+            mDialog = new MediaProjectionPermissionDialog(
+                    dialogContext,
+                    getMediaProjectionConfig(),
                     () -> {
                         MediaProjectionPermissionDialog dialog =
                                 (MediaProjectionPermissionDialog) mDialog;
                         ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
                         grantMediaProjectionPermission(selectedOption.getMode());
-                    }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
+                    },
+                    () -> finish(RECORD_CANCEL, /* projection= */ null),
+                    appName,
+                    mUid,
+                    mMediaProjectionMetricsLogger);
         } else {
             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext,
                     R.style.Theme_SystemUI_Dialog)
@@ -231,8 +246,20 @@
             mDialog = dialogBuilder.create();
         }
 
+        if (savedInstanceState == null) {
+            mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                    mUid,
+                    appName == null
+                            ? SessionCreationSource.CAST
+                            : SessionCreationSource.APP);
+        }
+
         setUpDialog(mDialog);
         mDialog.show();
+
+        if (savedInstanceState == null) {
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
+        }
     }
 
     @Override
@@ -309,6 +336,9 @@
                         projection.asBinder());
                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
                         getHostUserHandle());
+                intent.putExtra(
+                        MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID,
+                        getLaunchedFromUid());
                 intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired);
                 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index 2f10ad3..cff22b0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.media.projection.MediaProjectionConfig
 import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.res.R
 
 /** Dialog to select screen recording options */
@@ -26,12 +27,16 @@
     mediaProjectionConfig: MediaProjectionConfig?,
     private val onStartRecordingClicked: Runnable,
     private val onCancelClicked: Runnable,
-    private val appName: String?
+    private val appName: String?,
+    hostUid: Int,
+    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
     BaseScreenSharePermissionDialog(
         context,
         createOptionList(context, appName, mediaProjectionConfig),
-        appName
+        appName,
+        hostUid,
+        mediaProjectionMetricsLogger
     ) {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -89,15 +94,15 @@
                 }
             return listOf(
                 ScreenShareOption(
-                    mode = ENTIRE_SCREEN,
-                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
-                    warningText = entireScreenWarningText
-                ),
-                ScreenShareOption(
                     mode = SINGLE_APP,
                     spinnerText = R.string.screen_share_permission_dialog_option_single_app,
                     warningText = singleAppWarningText,
                     spinnerDisabledText = singleAppDisabledText,
+                ),
+                ScreenShareOption(
+                    mode = ENTIRE_SCREEN,
+                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+                    warningText = entireScreenWarningText
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index 37e8d9f..9bd5783 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -23,8 +23,8 @@
 @IntDef(ENTIRE_SCREEN, SINGLE_APP)
 annotation class ScreenShareMode
 
-const val ENTIRE_SCREEN = 0
-const val SINGLE_APP = 1
+const val SINGLE_APP = 0
+const val ENTIRE_SCREEN = 1
 
 class ScreenShareOption(
     @ScreenShareMode val mode: Int,
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 c4749e0..c77f3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -231,7 +231,6 @@
             animation.removeEndListener(this)
 
             if (!canceled) {
-
                 // The delay between finishing this animation and starting the runnable
                 val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
 
@@ -461,7 +460,6 @@
     }
 
     private fun handleMoveEvent(event: MotionEvent) {
-
         val x = event.x
         val y = event.y
 
@@ -927,17 +925,7 @@
             GestureState.ACTIVE -> {
                 previousXTranslationOnActiveOffset = previousXTranslation
                 updateRestingArrowDimens()
-                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                    vibratorHelper.performHapticFeedback(
-                        mView,
-                        HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
-                    )
-                } else {
-                    vibratorHelper.cancel()
-                    mainHandler.postDelayed(10L) {
-                        vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
-                    }
-                }
+                performActivatedHapticFeedback()
                 val popVelocity =
                     if (previousState == GestureState.INACTIVE) {
                         POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
@@ -958,25 +946,24 @@
 
                 mView.popOffEdge(POP_ON_INACTIVE_VELOCITY)
 
-                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                    vibratorHelper.performHapticFeedback(
-                        mView,
-                        HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
-                    )
-                } else {
-                    vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
-                }
+                performDeactivatedHapticFeedback()
                 updateRestingArrowDimens()
             }
             GestureState.FLUNG -> {
+                // Typically a vibration is only played while transitioning to ACTIVE. However there
+                // are instances where a fling to trigger back occurs while not in that state.
+                // (e.g. A fling is detected before crossing the trigger threshold.)
+                if (previousState != GestureState.ACTIVE) {
+                    performActivatedHapticFeedback()
+                }
                 mainHandler.postDelayed(POP_ON_FLING_DELAY) {
                     mView.popScale(POP_ON_FLING_VELOCITY)
                 }
-                updateRestingArrowDimens()
                 mainHandler.postDelayed(
                     onEndSetCommittedStateListener.runnable,
                     MIN_DURATION_FLING_ANIMATION
                 )
+                updateRestingArrowDimens()
             }
             GestureState.COMMITTED -> {
                 // In most cases, animating between states is handled via `updateRestingArrowDimens`
@@ -1011,6 +998,31 @@
         }
     }
 
+    private fun performDeactivatedHapticFeedback() {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            vibratorHelper.performHapticFeedback(
+                    mView,
+                    HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+            )
+        } else {
+            vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+        }
+    }
+
+    private fun performActivatedHapticFeedback() {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            vibratorHelper.performHapticFeedback(
+                    mView,
+                    HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+            )
+        } else {
+            vibratorHelper.cancel()
+            mainHandler.postDelayed(10L) {
+                vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+            }
+        }
+    }
+
     private fun convertVelocityToAnimationFactor(
         valueOnFastVelocity: Float,
         valueOnSlowVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 733383e..58c4f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -96,43 +96,9 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-/** Functions that help creating the People tile layouts. */
-public class PeopleTileViewHelper {
-    /** Turns on debugging information about People Space. */
-    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
-    private static final String TAG = "PeopleTileView";
-
-    private static final int DAYS_IN_A_WEEK = 7;
-    private static final int ONE_DAY = 1;
-
-    public static final int LAYOUT_SMALL = 0;
-    public static final int LAYOUT_MEDIUM = 1;
-    public static final int LAYOUT_LARGE = 2;
-
-    private static final int MIN_CONTENT_MAX_LINES = 2;
-    private static final int NAME_MAX_LINES_WITHOUT_LAST_INTERACTION = 3;
-    private static final int NAME_MAX_LINES_WITH_LAST_INTERACTION = 1;
-
-    private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_NOTIF_CONTENT = 16 + 22 + 8 + 16;
-    private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_STATUS_CONTENT = 16 + 16 + 24 + 4 + 16;
-    private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
-    private static final int MAX_MEDIUM_PADDING = 16;
-    private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
-    private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
-    private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
-    private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
-    private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
-
-    private static final int MESSAGES_COUNT_OVERFLOW = 6;
-
-    private static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
-
-    private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
-    private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
-    private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
-    private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
-
-    static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
+/** Variables and functions that is related to Emoji. */
+class EmojiHelper {
+    static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
 
     // This regex can be used to match Unicode emoji characters and character sequences. It's from
     // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor
@@ -165,7 +131,44 @@
                     + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
                     + "?)*";
 
-    private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+    static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+}
+
+/** Functions that help creating the People tile layouts. */
+public class PeopleTileViewHelper {
+    /** Turns on debugging information about People Space. */
+    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+    private static final String TAG = "PeopleTileView";
+
+    private static final int DAYS_IN_A_WEEK = 7;
+    private static final int ONE_DAY = 1;
+
+    public static final int LAYOUT_SMALL = 0;
+    public static final int LAYOUT_MEDIUM = 1;
+    public static final int LAYOUT_LARGE = 2;
+
+    private static final int MIN_CONTENT_MAX_LINES = 2;
+    private static final int NAME_MAX_LINES_WITHOUT_LAST_INTERACTION = 3;
+    private static final int NAME_MAX_LINES_WITH_LAST_INTERACTION = 1;
+
+    private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_NOTIF_CONTENT = 16 + 22 + 8 + 16;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_STATUS_CONTENT = 16 + 16 + 24 + 4 + 16;
+    private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
+    private static final int MAX_MEDIUM_PADDING = 16;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
+    private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
+    private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
+
+    private static final int MESSAGES_COUNT_OVERFLOW = 6;
+
+    private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
+    private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
+    private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
+    private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+
+    static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
 
     public static final String EMPTY_STRING = "";
 
@@ -831,7 +834,7 @@
 
         if (status.getActivity() == ACTIVITY_BIRTHDAY
                 || status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) {
-            setEmojiBackground(views, EMOJI_CAKE);
+            setEmojiBackground(views, EmojiHelper.EMOJI_CAKE);
         }
 
         Icon statusIcon = status.getIcon();
@@ -1072,7 +1075,7 @@
     /** Returns emoji if {@code message} has two of the same emoji in sequence. */
     @VisibleForTesting
     CharSequence getDoubleEmoji(CharSequence message) {
-        Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message);
+        Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message);
         // Stores the start and end indices of each matched emoji.
         List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>();
         // Stores each emoji text.
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index c567d56..10a2b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -31,10 +31,10 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.people.PeopleSpaceTileView
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.res.R
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -101,10 +101,10 @@
                                 view,
                                 priorityTiles,
                                 recentTiles,
-                                viewModel::onTileClicked,
+                                viewModel.onTileClicked,
                             )
                         } else {
-                            setNoConversationsContent(view, viewModel::onUserJourneyCancelled)
+                            setNoConversationsContent(view, viewModel.onUserJourneyCancelled)
                         }
                     }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
index ed7c21b..b847e95 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -23,35 +23,32 @@
 import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.res.R
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.people.PeopleSpaceUtils
 import com.android.systemui.people.PeopleTileViewHelper
 import com.android.systemui.people.data.model.PeopleTileModel
 import com.android.systemui.people.data.repository.PeopleTileRepository
 import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
+private const val TAG = "PeopleViewModel"
+
 /**
  * Models UI state for the people space, allowing the user to select which conversation should be
  * associated to a new or existing Conversation widget.
  */
 class PeopleViewModel(
-    @Application private val context: Context,
-    private val tileRepository: PeopleTileRepository,
-    private val widgetRepository: PeopleWidgetRepository,
-) : ViewModel() {
     /**
      * The list of the priority tiles/conversations.
      *
      * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
      * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
      */
-    private val _priorityTiles = MutableStateFlow(priorityTiles())
-    val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
+    val priorityTiles: StateFlow<List<PeopleTileViewModel>>,
 
     /**
      * The list of the priority tiles/conversations.
@@ -59,84 +56,29 @@
      * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
      * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
      */
-    private val _recentTiles = MutableStateFlow(recentTiles())
-    val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
+    val recentTiles: StateFlow<List<PeopleTileViewModel>>,
 
     /** The ID of the widget currently being edited/added. */
-    private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
-    val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow()
+    val appWidgetId: StateFlow<Int>,
 
     /** The result of this user journey. */
-    private val _result = MutableStateFlow<Result?>(null)
-    val result: StateFlow<Result?> = _result.asStateFlow()
+    val result: StateFlow<Result?>,
 
     /** Refresh the [priorityTiles] and [recentTiles]. */
-    fun onTileRefreshRequested() {
-        _priorityTiles.value = priorityTiles()
-        _recentTiles.value = recentTiles()
-    }
+    val onTileRefreshRequested: () -> Unit,
 
     /** Called when the [appWidgetId] should be changed to [widgetId]. */
-    fun onWidgetIdChanged(widgetId: Int) {
-        _appWidgetId.value = widgetId
-    }
+    val onWidgetIdChanged: (widgetId: Int) -> Unit,
 
     /** Clear [result], setting it to null. */
-    fun clearResult() {
-        _result.value = null
-    }
+    val clearResult: () -> Unit,
 
     /** Called when a tile is clicked. */
-    fun onTileClicked(tile: PeopleTileViewModel) {
-        val widgetId = _appWidgetId.value
-        if (PeopleSpaceUtils.DEBUG) {
-            Log.d(
-                TAG,
-                "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
-            )
-        }
-        widgetRepository.setWidgetTile(widgetId, tile.key)
-        _result.value =
-            Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) })
-    }
+    val onTileClicked: (tile: PeopleTileViewModel) -> Unit,
 
     /** Called when this user journey is cancelled. */
-    fun onUserJourneyCancelled() {
-        _result.value = Result.Cancelled
-    }
-
-    private fun priorityTiles(): List<PeopleTileViewModel> {
-        return try {
-            tileRepository.priorityTiles().map { it.toViewModel() }
-        } catch (e: Exception) {
-            Log.e(TAG, "Couldn't retrieve priority conversations", e)
-            emptyList()
-        }
-    }
-
-    private fun recentTiles(): List<PeopleTileViewModel> {
-        return try {
-            tileRepository.recentTiles().map { it.toViewModel() }
-        } catch (e: Exception) {
-            Log.e(TAG, "Couldn't retrieve recent conversations", e)
-            emptyList()
-        }
-    }
-
-    private fun PeopleTileModel.toViewModel(): PeopleTileViewModel {
-        val icon =
-            PeopleTileViewHelper.getPersonIconBitmap(
-                context,
-                this,
-                PeopleTileViewHelper.getSizeInDp(
-                    context,
-                    R.dimen.avatar_size_for_medium,
-                    context.resources.displayMetrics.density,
-                )
-            )
-        return PeopleTileViewModel(key, icon, username)
-    }
-
+    val onUserJourneyCancelled: () -> Unit,
+) : ViewModel() {
     /** The Factory that should be used to create a [PeopleViewModel]. */
     class Factory
     @Inject
@@ -153,10 +95,94 @@
 
     sealed class Result {
         class Success(val data: Intent) : Result()
+
         object Cancelled : Result()
     }
+}
 
-    companion object {
-        private const val TAG = "PeopleViewModel"
+private fun PeopleViewModel(
+    @Application context: Context,
+    tileRepository: PeopleTileRepository,
+    widgetRepository: PeopleWidgetRepository,
+): PeopleViewModel {
+    fun priorityTiles(): List<PeopleTileViewModel> {
+        return try {
+            tileRepository.priorityTiles().map { it.toViewModel(context) }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve priority conversations", e)
+            emptyList()
+        }
     }
+
+    fun recentTiles(): List<PeopleTileViewModel> {
+        return try {
+            tileRepository.recentTiles().map { it.toViewModel(context) }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve recent conversations", e)
+            emptyList()
+        }
+    }
+
+    val priorityTiles = MutableStateFlow(priorityTiles())
+    val recentTiles = MutableStateFlow(recentTiles())
+    val appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
+    val result = MutableStateFlow<PeopleViewModel.Result?>(null)
+
+    fun onTileRefreshRequested() {
+        priorityTiles.value = priorityTiles()
+        recentTiles.value = recentTiles()
+    }
+
+    fun onWidgetIdChanged(widgetId: Int) {
+        appWidgetId.value = widgetId
+    }
+
+    fun clearResult() {
+        result.value = null
+    }
+
+    fun onTileClicked(tile: PeopleTileViewModel) {
+        val widgetId = appWidgetId.value
+        if (PeopleSpaceUtils.DEBUG) {
+            Log.d(
+                TAG,
+                "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
+            )
+        }
+        widgetRepository.setWidgetTile(widgetId, tile.key)
+        result.value =
+            PeopleViewModel.Result.Success(
+                Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }
+            )
+    }
+
+    fun onUserJourneyCancelled() {
+        result.value = PeopleViewModel.Result.Cancelled
+    }
+
+    return PeopleViewModel(
+        priorityTiles = priorityTiles.asStateFlow(),
+        recentTiles = recentTiles.asStateFlow(),
+        appWidgetId = appWidgetId.asStateFlow(),
+        result = result.asStateFlow(),
+        onTileRefreshRequested = ::onTileRefreshRequested,
+        onWidgetIdChanged = ::onWidgetIdChanged,
+        clearResult = ::clearResult,
+        onTileClicked = ::onTileClicked,
+        onUserJourneyCancelled = ::onUserJourneyCancelled,
+    )
+}
+
+fun PeopleTileModel.toViewModel(@Application context: Context): PeopleTileViewModel {
+    val icon =
+        PeopleTileViewHelper.getPersonIconBitmap(
+            context,
+            this,
+            PeopleTileViewHelper.getSizeInDp(
+                context,
+                R.dimen.avatar_size_for_medium,
+                context.resources.displayMetrics.density,
+            )
+        )
+    return PeopleTileViewModel(key, icon, username)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 38204ab..a6c6233 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -64,14 +64,13 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
-import com.android.settingslib.utils.PowerUtil;
-import com.android.systemui.res.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
+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.BatteryController;
@@ -376,14 +375,6 @@
                 TAG_AUTO_SAVER, SystemMessage.NOTE_AUTO_SAVER_SUGGESTION, n, UserHandle.ALL);
     }
 
-    private String getHybridContentString(String percentage) {
-        return PowerUtil.getBatteryRemainingStringFormatted(
-                mContext,
-                mCurrentBatterySnapshot.getTimeRemainingMillis(),
-                percentage,
-                mCurrentBatterySnapshot.isBasedOnUsage());
-    }
-
     private PendingIntent pendingBroadcast(String action) {
         return PendingIntent.getBroadcastAsUser(
                 mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index d949a2a..67d390d 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.asIndenting
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.withIncreasedIndent
 import java.io.PrintWriter
@@ -144,6 +145,7 @@
         ipw.flush()
     }
 
+    @WeaklyReferencedCallback
     interface Callback {
         fun onFlagMicCameraChanged(flag: Boolean) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index 68bf88b..3b3844a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -74,6 +74,7 @@
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(getView());
         mQsImpl = mQsImplProvider.get();
+        mQsImpl.onCreate(null);
         mQsImpl.onComponentCreated(qsFragmentComponent, savedInstanceState);
     }
 
@@ -85,21 +86,13 @@
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (mQsImpl != null) {
-            mQsImpl.onCreate(savedInstanceState);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
+    public void onDestroyView() {
         if (mQsImpl != null) {
             mQsImpl.onDestroy();
+            mQsImpl = null;
         }
+        super.onDestroyView();
     }
-
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 4aad6a0..fab7e95 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -170,6 +170,7 @@
     private CommandQueue mCommandQueue;
 
     private View mRootView;
+    private View mFooterActionsView;
 
     @Inject
     public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -285,6 +286,7 @@
         if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)
                 || !ComposeFacade.INSTANCE.isComposeAvailable()) {
             Log.d(TAG, "Binding the View implementation of the QS footer actions");
+            mFooterActionsView = footerActionsView;
             mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
                     mListeningAndVisibilityLifecycleOwner);
             return;
@@ -294,6 +296,7 @@
         Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
         View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
                 mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
+        mFooterActionsView = composeView;
 
         // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
         // to all views except for qs_footer_actions, so we set it to the Compose view.
@@ -472,7 +475,7 @@
         boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
                 || mHeaderAnimating || mShowCollapsedOnKeyguard);
         mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
-        mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
+        mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                 || (mQsExpanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -856,7 +859,7 @@
         boolean customizing = isCustomizing();
         mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
-        mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
+        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.
@@ -940,7 +943,7 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ "  ");
-        indentingPw.println("QSFragment:");
+        indentingPw.println("QSImpl:");
         indentingPw.increaseIndent();
         indentingPw.println("mQsBounds: " + mQsBounds);
         indentingPw.println("mQsExpanded: " + mQsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index fc2f5f9..11db69b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -174,7 +174,9 @@
 
     @Override
     public void destroy() {
-        super.destroy();
+        // Don't call super as this may be called before the view is dettached and calling super
+        // will remove the attach listener. We don't need to do that, because once this object is
+        // detached from the graph, it will be gc.
         mHost.removeCallback(mQSHostCallback);
 
         for (TileRecord record : mRecords) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 7794fa0..eb11568 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -20,15 +20,14 @@
 import android.os.Handler;
 
 import com.android.systemui.statusbar.policy.Listenable;
-import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.settings.SettingsProxy;
 import com.android.systemui.util.settings.SystemSettings;
 
 /**
- * Helper for managing secure, global, and system settings through use of {@link SettingsProxy},
- * which is the common superclass of {@link SecureSettings}, {@link GlobalSettings}, and
- * {@link SystemSettings}.
+ * Helper for managing global settings through use of {@link SettingsProxy}. This should
+ * <em>not</em> be used for {@link SecureSettings} or {@link SystemSettings} since those must be
+ * user-aware (instead, use {@link UserSettingObserver}).
  */
 public abstract class SettingObserver extends ContentObserver implements Listenable {
     private final SettingsProxy mSettingsProxy;
@@ -36,23 +35,20 @@
     private final int mDefaultValue;
 
     private boolean mListening;
-    private int mUserId;
     private int mObservedValue;
 
     protected abstract void handleValueChanged(int value, boolean observedChange);
 
-    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
-            int userId) {
-        this(settingsProxy, handler, settingName, userId, 0);
+    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
+        this(settingsProxy, handler, settingName, 0);
     }
 
     public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
-            int userId, int defaultValue) {
+            int defaultValue) {
         super(handler);
         mSettingsProxy = settingsProxy;
         mSettingName = settingName;
         mObservedValue = mDefaultValue = defaultValue;
-        mUserId = userId;
     }
 
     public int getValue() {
@@ -65,11 +61,11 @@
      * @param value The new value for the setting.
      */
     public void setValue(int value) {
-        mSettingsProxy.putIntForUser(mSettingName, value, mUserId);
+        mSettingsProxy.putInt(mSettingName, value);
     }
 
     private int getValueFromProvider() {
-        return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId);
+        return mSettingsProxy.getInt(mSettingName, mDefaultValue);
     }
 
     @Override
@@ -78,8 +74,8 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserverForUser(
-                    mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+            mSettingsProxy.registerContentObserver(
+                    mSettingsProxy.getUriFor(mSettingName), false, this);
         } else {
             mSettingsProxy.unregisterContentObserver(this);
             mObservedValue = mDefaultValue;
@@ -94,21 +90,6 @@
         handleValueChanged(value, changed);
     }
 
-    /**
-     * Set user handle for which to observe the setting.
-     */
-    public void setUserId(int userId) {
-        mUserId = userId;
-        if (mListening) {
-            setListening(false);
-            setListening(true);
-        }
-    }
-
-    public int getCurrentUser() {
-        return mUserId;
-    }
-
     public String getKey() {
         return mSettingName;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
index 11759f7..777faea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.util.AttributeSet
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.flags.RefactorFlag
 import com.android.systemui.res.R
 
 open class SideLabelTileLayout(
@@ -27,8 +27,8 @@
     attrs: AttributeSet?
 ) : TileLayout(context, attrs) {
 
-    private final val isSmallLandscapeLockscreenEnabled =
-            ViewRefactorFlag(flag = Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled
+    private val isSmallLandscapeLockscreenEnabled =
+            RefactorFlag.forView(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled
 
     override fun updateResources(): Boolean {
         return super.updateResources().also {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 9ba1645..9d4eba5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -15,13 +15,13 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.FontSizeUtils;
-import com.android.systemui.flags.ViewRefactorFlag;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
 import com.android.systemui.qs.tileimpl.HeightOverrideable;
 import com.android.systemui.qs.tileimpl.QSTileViewImplKt;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 
@@ -55,7 +55,7 @@
     protected int mLastTileBottom;
     protected TextView mTempTextView;
     private final Boolean mIsSmallLandscapeLockscreenEnabled =
-            new ViewRefactorFlag(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled();
+            RefactorFlag.forView(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled();
 
     public TileLayout(Context context) {
         this(context, null);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
new file mode 100644
index 0000000..539c2d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+
+import com.android.systemui.statusbar.policy.Listenable;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
+import com.android.systemui.util.settings.UserSettingsProxy;
+
+/**
+ * Helper for managing secure and system settings through use of {@link UserSettingsProxy},
+ * which is the common superclass of {@link SecureSettings} and {@link SystemSettings}.
+ */
+public abstract class UserSettingObserver extends ContentObserver implements Listenable {
+    private final UserSettingsProxy mSettingsProxy;
+    private final String mSettingName;
+    private final int mDefaultValue;
+
+    private boolean mListening;
+    private int mUserId;
+    private int mObservedValue;
+
+    protected abstract void handleValueChanged(int value, boolean observedChange);
+
+    public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName,
+            int userId) {
+        this(settingsProxy, handler, settingName, userId, 0);
+    }
+
+    public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName,
+            int userId, int defaultValue) {
+        super(handler);
+        mSettingsProxy = settingsProxy;
+        mSettingName = settingName;
+        mObservedValue = mDefaultValue = defaultValue;
+        mUserId = userId;
+    }
+
+    public int getValue() {
+        return mListening ? mObservedValue : getValueFromProvider();
+    }
+
+    /**
+     * Set the value of the observed setting.
+     *
+     * @param value The new value for the setting.
+     */
+    public void setValue(int value) {
+        mSettingsProxy.putIntForUser(mSettingName, value, mUserId);
+    }
+
+    private int getValueFromProvider() {
+        return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening == mListening) return;
+        mListening = listening;
+        if (listening) {
+            mObservedValue = getValueFromProvider();
+            mSettingsProxy.registerContentObserverForUser(
+                    mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+        } else {
+            mSettingsProxy.unregisterContentObserver(this);
+            mObservedValue = mDefaultValue;
+        }
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        final int value = getValueFromProvider();
+        final boolean changed = value != mObservedValue;
+        mObservedValue = value;
+        handleValueChanged(value, changed);
+    }
+
+    /**
+     * Set user handle for which to observe the setting.
+     */
+    public void setUserId(int userId) {
+        mUserId = userId;
+        if (mListening) {
+            setListening(false);
+            setListening(true);
+        }
+    }
+
+    public int getCurrentUser() {
+        return mUserId;
+    }
+
+    public String getKey() {
+        return mSettingName;
+    }
+
+    public boolean isListening() {
+        return mListening;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index a65967a..bd4c6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -31,7 +31,7 @@
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel;
+import com.android.systemui.qs.tiles.di.QSTilesModule;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -60,20 +60,18 @@
                 QSFlagsModule.class,
                 QSHostModule.class,
                 QSPipelineModule.class,
+                QSTilesModule.class,
         }
 )
 public interface QSModule {
 
-    /** A map of internal QS tiles. Ensures that this can be injected even if
-     * it is empty */
+    /**
+     * A map of internal QS tiles. Ensures that this can be injected even if
+     * it is empty
+     */
     @Multibinds
     Map<String, QSTileImpl<?>> tileMap();
 
-    /** A map of internal QS tile ViewModels. Ensures that this can be injected even if
-     * it is empty */
-    @Multibinds
-    Map<String, QSTileViewModel> tileViewModelMap();
-
     @Provides
     @SysUISingleton
     static AutoTileManager provideAutoTileManager(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 051eeb0..bd13d06 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -538,12 +539,17 @@
             Log.i(TAG, "Launching activity before click");
         } else {
             Log.i(TAG, "The activity is starting");
-            ActivityLaunchAnimator.Controller controller = mViewClicked == null
-                    ? null
-                    : ActivityLaunchAnimator.Controller.fromView(mViewClicked, 0);
-            mUiHandler.post(() ->
-                    mActivityStarter.startPendingIntentDismissingKeyguard(
-                            pendingIntent, null, controller)
+
+            ActivityLaunchAnimator.Controller controller =
+                    mViewClicked == null ? null :
+                    ActivityLaunchAnimator.Controller.fromView(
+                            mViewClicked,
+                            InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+                    );
+            mActivityStarter.startPendingIntentMaybeDismissingKeyguard(
+                    pendingIntent,
+                    /* intentSentUiThreadCallback= */ null,
+                    controller
             );
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 2469a98..78f2da5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -17,6 +17,7 @@
 
 import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
 
+import android.app.ActivityManager;
 import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -35,6 +36,7 @@
 import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.TileService;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -81,7 +83,8 @@
 
     // Bind retry control.
     private static final int MAX_BIND_RETRIES = 5;
-    private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
+    private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS;
+    private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS;
 
     // Shared prefs that hold tile lifecycle info.
     private static final String TILES = "tiles_prefs";
@@ -94,6 +97,7 @@
     private final IBinder mToken = new Binder();
     private final PackageManagerAdapter mPackageManagerAdapter;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final ActivityManager mActivityManager;
 
     private Set<Integer> mQueuedMessages = new ArraySet<>();
     @Nullable
@@ -102,7 +106,8 @@
     private IBinder mClickBinder;
 
     private int mBindTryCount;
-    private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
+    private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
+    private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false);
     private AtomicBoolean mBound = new AtomicBoolean(false);
     private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false);
     private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false);
@@ -115,7 +120,7 @@
     @AssistedInject
     TileLifecycleManager(@Main Handler handler, Context context, IQSService service,
             PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher,
-            @Assisted Intent intent, @Assisted UserHandle user,
+            @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager,
             @Background DelayableExecutor executor) {
         mContext = context;
         mHandler = handler;
@@ -126,6 +131,7 @@
         mExecutor = executor;
         mPackageManagerAdapter = packageManagerAdapter;
         mBroadcastDispatcher = broadcastDispatcher;
+        mActivityManager = activityManager;
         if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
     }
 
@@ -152,10 +158,6 @@
         }
     }
 
-    public void setBindRetryDelay(int delayMs) {
-        mBindRetryDelay = delayMs;
-    }
-
     public boolean isActiveTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
@@ -250,19 +252,15 @@
 
     private boolean bindServices() {
         String packageName = mIntent.getComponent().getPackageName();
+        int flags = Context.BIND_AUTO_CREATE
+                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+                | Context.BIND_WAIVE_PRIORITY;
         if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName,
                 mUser)) {
-            return mContext.bindServiceAsUser(mIntent, this,
-                    Context.BIND_AUTO_CREATE
-                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                            | Context.BIND_WAIVE_PRIORITY,
-                    mUser);
+            return mContext.bindServiceAsUser(mIntent, this, flags, mUser);
         }
         return mContext.bindServiceAsUser(mIntent, this,
-                Context.BIND_AUTO_CREATE
-                        | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
-                        | Context.BIND_WAIVE_PRIORITY,
+                flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                 mUser);
     }
 
@@ -352,10 +350,34 @@
         if (!mBound.get()) return;
         if (DEBUG) Log.d(TAG, "handleDeath");
         if (checkComponentState()) {
-            mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay);
+            if (isDeathRebindScheduled.compareAndSet(false, true)) {
+                mExecutor.executeDelayed(() -> {
+                    setBindService(true);
+                    isDeathRebindScheduled.set(false);
+                }, getRebindDelay());
+            }
         }
     }
 
+    /**
+     * @return the delay to automatically rebind after a service died. It provides a longer delay if
+     * the device is a low memory state because the service is likely to get killed again by the
+     * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds.
+     */
+    private long getRebindDelay() {
+        final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
+        mActivityManager.getMemoryInfo(info);
+
+        final long delay;
+        if (info.lowMemory) {
+            delay = LOW_MEMORY_BIND_RETRY_DELAY;
+        } else {
+            delay = mBindRetryDelay;
+        }
+        Log.i(TAG, "Rebinding with a delay=" + delay);
+        return delay;
+    }
+
     private boolean checkComponentState() {
         if (!isPackageAvailable() || !isComponentAvailable()) {
             startPackageListening();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 941a9d6..3ee4a1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -75,13 +75,12 @@
     private boolean mStarted = false;
 
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
-            BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
-            CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
+            UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory,
+            CustomTileAddedRepository customTileAddedRepository) {
         this(tileServices, handler, userTracker, customTileAddedRepository,
-                new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
-                        new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
+                tileLifecycleManagerFactory.create(
                         new Intent(TileService.ACTION_QS_TILE).setComponent(component),
-                        userTracker.getUserHandle(), executor));
+                        userTracker.getUserHandle()));
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index acee8e9..c3744df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -81,6 +81,7 @@
     private final UserTracker mUserTracker;
     private final StatusBarIconController mStatusBarIconController;
     private final PanelInteractor mPanelInteractor;
+    private final TileLifecycleManager.Factory mTileLifecycleManagerFactory;
     private final CustomTileAddedRepository mCustomTileAddedRepository;
     private final DelayableExecutor mBackgroundExecutor;
 
@@ -96,6 +97,7 @@
             CommandQueue commandQueue,
             StatusBarIconController statusBarIconController,
             PanelInteractor panelInteractor,
+            TileLifecycleManager.Factory tileLifecycleManagerFactory,
             CustomTileAddedRepository customTileAddedRepository,
             @Background DelayableExecutor backgroundExecutor) {
         mHost = host;
@@ -109,6 +111,7 @@
         mStatusBarIconController = statusBarIconController;
         mCommandQueue.addCallback(mRequestListeningCallback);
         mPanelInteractor = panelInteractor;
+        mTileLifecycleManagerFactory = tileLifecycleManagerFactory;
         mCustomTileAddedRepository = customTileAddedRepository;
         mBackgroundExecutor = backgroundExecutor;
     }
@@ -137,8 +140,8 @@
 
     protected TileServiceManager onCreateTileService(ComponentName component,
             BroadcastDispatcher broadcastDispatcher) {
-        return new TileServiceManager(this, mHandlerProvider.get(), component,
-                broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor);
+        return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker,
+                mTileLifecycleManagerFactory, mCustomTileAddedRepository);
     }
 
     public void freeService(CustomTileInterface tile, TileServiceManager service) {
@@ -323,7 +326,7 @@
                 if (info.applicationInfo.isSystemApp()) {
                     final StatusBarIcon statusIcon = icon != null
                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
-                                    contentDescription)
+                            contentDescription)
                             : null;
                     final String slot = getStatusBarIconSlotName(componentName);
                     mMainHandler.post(new Runnable() {
@@ -356,11 +359,11 @@
         synchronized (mServices) {
             mTokenMap.forEach((iBinder, customTile) ->
                     sb.append(iBinder.toString())
-                    .append(":")
-                    .append(customTile.getComponent().flattenToShortString())
-                    .append(":")
-                    .append(customTile.getUser())
-                    .append(","));
+                            .append(":")
+                            .append(customTile.getComponent().flattenToShortString())
+                            .append(":")
+                            .append(customTile.getUser())
+                            .append(","));
         }
         sb.append("]");
         return sb.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index c91ed13..8e30740 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.user.data.repository.UserSwitcherRepository
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -102,7 +102,7 @@
     private val deviceProvisionedController: DeviceProvisionedController,
     private val qsSecurityFooterUtils: QSSecurityFooterUtils,
     private val fgsManagerController: FgsManagerController,
-    private val userInteractor: UserInteractor,
+    private val userSwitcherInteractor: UserSwitcherInteractor,
     securityRepository: SecurityRepository,
     foregroundServicesRepository: ForegroundServicesRepository,
     userSwitcherRepository: UserSwitcherRepository,
@@ -178,6 +178,6 @@
     }
 
     override fun showUserSwitcher(expandable: Expandable) {
-        userInteractor.showUserSwitcher(expandable)
+        userSwitcherInteractor.showUserSwitcher(expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index d09b210..0995dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -24,13 +24,11 @@
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.TextView
-import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.dagger.SysUISingleton
@@ -40,6 +38,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
@@ -98,10 +97,6 @@
         var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
         var previousUserSwitcher: FooterActionsButtonViewModel? = null
 
-        // Set the initial visibility on the View directly so that we don't briefly show it for a
-        // few frames before [viewModel.isVisible] is collected.
-        view.isInvisible = !viewModel.isVisible.value
-
         // Listen for ViewModel updates when the View is attached.
         view.repeatWhenAttached {
             val attachedScope = this.lifecycleScope
@@ -111,12 +106,7 @@
                 // TODO(b/242040009): Should this move somewhere else?
                 launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) }
 
-                // Make sure we set the correct visibility and alpha even when QS are not currently
-                // shown.
-                launch {
-                    viewModel.isVisible.collect { isVisible -> view.isInvisible = !isVisible }
-                }
-
+                // Make sure we set the correct alphas even when QS are not currently shown.
                 launch { viewModel.alpha.collect { view.alpha = it } }
                 launch {
                     viewModel.backgroundAlpha.collect {
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 64fa33c..aff4a67 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
@@ -33,6 +33,7 @@
 import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
 import com.android.systemui.res.R
 import com.android.systemui.util.icuMessageFormat
 import javax.inject.Inject
@@ -47,25 +48,35 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
+private const val TAG = "FooterActionsViewModel"
+
 /** A ViewModel for the footer actions. */
 class FooterActionsViewModel(
-    @Application appContext: Context,
-    private val footerActionsInteractor: FooterActionsInteractor,
-    private val falsingManager: FalsingManager,
-    private val globalActionsDialogLite: GlobalActionsDialogLite,
-    showPowerButton: Boolean,
-) {
-    /** The context themed with the Quick Settings colors. */
-    private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+    /** The model for the security button. */
+    val security: Flow<FooterActionsSecurityButtonViewModel?>,
+
+    /** The model for the foreground services button. */
+    val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?>,
+
+    /** The model for the user switcher button. */
+    val userSwitcher: Flow<FooterActionsButtonViewModel?>,
+
+    /** The model for the settings button. */
+    val settings: FooterActionsButtonViewModel,
+
+    /** The model for the power button. */
+    val power: FooterActionsButtonViewModel?,
 
     /**
-     * Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
-     * the UI should still participate to the layout it is included in (i.e. in the View world it
-     * should be INVISIBLE, not GONE).
+     * Observe the device monitoring dialog requests and show the dialog accordingly. This function
+     * will suspend indefinitely and will need to be cancelled to stop observing.
+     *
+     * Important: [quickSettingsContext] must be the [Context] associated to the
+     * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
+     * function must be cancelled when that fragment is destroyed.
      */
-    private val _isVisible = MutableStateFlow(false)
-    val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
-
+    val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit,
+) {
     /** The alpha the UI rendering this ViewModel should have. */
     private val _alpha = MutableStateFlow(1f)
     val alpha: StateFlow<Float> = _alpha.asStateFlow()
@@ -74,111 +85,6 @@
     private val _backgroundAlpha = MutableStateFlow(1f)
     val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow()
 
-    /** The model for the security button. */
-    val security: Flow<FooterActionsSecurityButtonViewModel?> =
-        footerActionsInteractor.securityButtonConfig
-            .map { config ->
-                val (icon, text, isClickable) = config ?: return@map null
-                FooterActionsSecurityButtonViewModel(
-                    icon,
-                    text,
-                    if (isClickable) this::onSecurityButtonClicked else null,
-                )
-            }
-            .distinctUntilChanged()
-
-    /** The model for the foreground services button. */
-    val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> =
-        combine(
-                footerActionsInteractor.foregroundServicesCount,
-                footerActionsInteractor.hasNewForegroundServices,
-                security,
-            ) { foregroundServicesCount, hasNewChanges, securityModel ->
-                if (foregroundServicesCount <= 0) {
-                    return@combine null
-                }
-
-                val text =
-                    icuMessageFormat(
-                        context.resources,
-                        R.string.fgs_manager_footer_label,
-                        foregroundServicesCount,
-                    )
-                FooterActionsForegroundServicesButtonViewModel(
-                    foregroundServicesCount,
-                    text = text,
-                    displayText = securityModel == null,
-                    hasNewChanges = hasNewChanges,
-                    this::onForegroundServiceButtonClicked,
-                )
-            }
-            .distinctUntilChanged()
-
-    /** The model for the user switcher button. */
-    val userSwitcher: Flow<FooterActionsButtonViewModel?> =
-        footerActionsInteractor.userSwitcherStatus
-            .map { userSwitcherStatus ->
-                when (userSwitcherStatus) {
-                    UserSwitcherStatusModel.Disabled -> null
-                    is UserSwitcherStatusModel.Enabled -> {
-                        if (userSwitcherStatus.currentUserImage == null) {
-                            Log.e(
-                                TAG,
-                                "Skipped the addition of user switcher button because " +
-                                    "currentUserImage is missing",
-                            )
-                            return@map null
-                        }
-
-                        userSwitcherButton(userSwitcherStatus)
-                    }
-                }
-            }
-            .distinctUntilChanged()
-
-    /** The model for the settings button. */
-    val settings: FooterActionsButtonViewModel =
-        FooterActionsButtonViewModel(
-            id = R.id.settings_button_container,
-            Icon.Resource(
-                R.drawable.ic_settings,
-                ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
-            ),
-            iconTint =
-                Utils.getColorAttrDefaultColor(
-                    context,
-                    R.attr.onShadeInactiveVariant,
-                ),
-            backgroundColor = R.attr.shadeInactive,
-            this::onSettingsButtonClicked,
-        )
-
-    /** The model for the power button. */
-    val power: FooterActionsButtonViewModel? =
-        if (showPowerButton) {
-            FooterActionsButtonViewModel(
-                id = R.id.pm_lite,
-                Icon.Resource(
-                    android.R.drawable.ic_lock_power_off,
-                    ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
-                ),
-                iconTint =
-                    Utils.getColorAttrDefaultColor(
-                        context,
-                        R.attr.onShadeActive,
-                    ),
-                backgroundColor = R.attr.shadeActive,
-                this::onPowerButtonClicked,
-            )
-        } else {
-            null
-        }
-
-    /** Called when the visibility of the UI rendering this model should be changed. */
-    fun onVisibilityChangeRequested(visible: Boolean) {
-        _isVisible.value = visible
-    }
-
     /** Called when the expansion of the Quick Settings changed. */
     fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
         if (isInSplitShade) {
@@ -195,89 +101,6 @@
         }
     }
 
-    /**
-     * Observe the device monitoring dialog requests and show the dialog accordingly. This function
-     * will suspend indefinitely and will need to be cancelled to stop observing.
-     *
-     * Important: [quickSettingsContext] must be the [Context] associated to the
-     * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
-     * function must be cancelled when that fragment is destroyed.
-     */
-    suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
-        footerActionsInteractor.deviceMonitoringDialogRequests.collect {
-            footerActionsInteractor.showDeviceMonitoringDialog(
-                quickSettingsContext,
-                expandable = null,
-            )
-        }
-    }
-
-    private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
-    }
-
-    private fun onForegroundServiceButtonClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showForegroundServicesDialog(expandable)
-    }
-
-    private fun onUserSwitcherClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showUserSwitcher(expandable)
-    }
-
-    private fun onSettingsButtonClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showSettings(expandable)
-    }
-
-    private fun onPowerButtonClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
-    }
-
-    private fun userSwitcherButton(
-        status: UserSwitcherStatusModel.Enabled
-    ): FooterActionsButtonViewModel {
-        val icon = status.currentUserImage!!
-
-        return FooterActionsButtonViewModel(
-            id = R.id.multi_user_switch,
-            icon =
-                Icon.Loaded(
-                    icon,
-                    ContentDescription.Loaded(
-                        userSwitcherContentDescription(status.currentUserName)
-                    ),
-                ),
-            iconTint = null,
-            backgroundColor = R.attr.shadeInactive,
-            onClick = this::onUserSwitcherClicked,
-        )
-    }
-
-    private fun userSwitcherContentDescription(currentUser: String?): String? {
-        return currentUser?.let { user ->
-            context.getString(R.string.accessibility_quick_settings_user, user)
-        }
-    }
-
     @SysUISingleton
     class Factory
     @Inject
@@ -315,8 +138,237 @@
             )
         }
     }
+}
 
-    companion object {
-        private const val TAG = "FooterActionsViewModel"
+fun FooterActionsViewModel(
+    @Application appContext: Context,
+    footerActionsInteractor: FooterActionsInteractor,
+    falsingManager: FalsingManager,
+    globalActionsDialogLite: GlobalActionsDialogLite,
+    showPowerButton: Boolean,
+): FooterActionsViewModel {
+    suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
+        footerActionsInteractor.deviceMonitoringDialogRequests.collect {
+            footerActionsInteractor.showDeviceMonitoringDialog(
+                quickSettingsContext,
+                expandable = null,
+            )
+        }
     }
+
+    fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+    }
+
+    fun onForegroundServiceButtonClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showForegroundServicesDialog(expandable)
+    }
+
+    fun onUserSwitcherClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showUserSwitcher(expandable)
+    }
+
+    fun onSettingsButtonClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showSettings(expandable)
+    }
+
+    fun onPowerButtonClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
+    }
+
+    val qsThemedContext = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+
+    val security =
+        footerActionsInteractor.securityButtonConfig
+            .map { config ->
+                config?.let { securityButtonViewModel(it, ::onSecurityButtonClicked) }
+            }
+            .distinctUntilChanged()
+
+    val foregroundServices =
+        combine(
+                footerActionsInteractor.foregroundServicesCount,
+                footerActionsInteractor.hasNewForegroundServices,
+                security,
+            ) { foregroundServicesCount, hasNewChanges, securityModel ->
+                if (foregroundServicesCount <= 0) {
+                    return@combine null
+                }
+
+                foregroundServicesButtonViewModel(
+                    qsThemedContext,
+                    foregroundServicesCount,
+                    securityModel,
+                    hasNewChanges,
+                    ::onForegroundServiceButtonClicked,
+                )
+            }
+            .distinctUntilChanged()
+
+    val userSwitcher =
+        footerActionsInteractor.userSwitcherStatus
+            .map { userSwitcherStatus ->
+                when (userSwitcherStatus) {
+                    UserSwitcherStatusModel.Disabled -> null
+                    is UserSwitcherStatusModel.Enabled -> {
+                        if (userSwitcherStatus.currentUserImage == null) {
+                            Log.e(
+                                TAG,
+                                "Skipped the addition of user switcher button because " +
+                                    "currentUserImage is missing",
+                            )
+                            return@map null
+                        }
+
+                        userSwitcherButtonViewModel(
+                            qsThemedContext,
+                            userSwitcherStatus,
+                            ::onUserSwitcherClicked
+                        )
+                    }
+                }
+            }
+            .distinctUntilChanged()
+
+    val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
+    val power =
+        if (showPowerButton) {
+            powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked)
+        } else {
+            null
+        }
+
+    return FooterActionsViewModel(
+        security = security,
+        foregroundServices = foregroundServices,
+        userSwitcher = userSwitcher,
+        settings = settings,
+        power = power,
+        observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests,
+    )
+}
+
+fun securityButtonViewModel(
+    config: SecurityButtonConfig,
+    onSecurityButtonClicked: (Context, Expandable) -> Unit,
+): FooterActionsSecurityButtonViewModel {
+    val (icon, text, isClickable) = config
+    return FooterActionsSecurityButtonViewModel(
+        icon,
+        text,
+        if (isClickable) onSecurityButtonClicked else null,
+    )
+}
+
+fun foregroundServicesButtonViewModel(
+    qsThemedContext: Context,
+    foregroundServicesCount: Int,
+    securityModel: FooterActionsSecurityButtonViewModel?,
+    hasNewChanges: Boolean,
+    onForegroundServiceButtonClicked: (Expandable) -> Unit,
+): FooterActionsForegroundServicesButtonViewModel {
+    val text =
+        icuMessageFormat(
+            qsThemedContext.resources,
+            R.string.fgs_manager_footer_label,
+            foregroundServicesCount,
+        )
+
+    return FooterActionsForegroundServicesButtonViewModel(
+        foregroundServicesCount,
+        text = text,
+        displayText = securityModel == null,
+        hasNewChanges = hasNewChanges,
+        onForegroundServiceButtonClicked,
+    )
+}
+
+fun userSwitcherButtonViewModel(
+    qsThemedContext: Context,
+    status: UserSwitcherStatusModel.Enabled,
+    onUserSwitcherClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+    val icon = status.currentUserImage!!
+    return FooterActionsButtonViewModel(
+        id = R.id.multi_user_switch,
+        icon =
+            Icon.Loaded(
+                icon,
+                ContentDescription.Loaded(
+                    userSwitcherContentDescription(qsThemedContext, status.currentUserName)
+                ),
+            ),
+        iconTint = null,
+        backgroundColor = R.attr.shadeInactive,
+        onClick = onUserSwitcherClicked,
+    )
+}
+
+private fun userSwitcherContentDescription(
+    qsThemedContext: Context,
+    currentUser: String?
+): String? {
+    return currentUser?.let { user ->
+        qsThemedContext.getString(R.string.accessibility_quick_settings_user, user)
+    }
+}
+
+fun settingsButtonViewModel(
+    qsThemedContext: Context,
+    onSettingsButtonClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+    return FooterActionsButtonViewModel(
+        id = R.id.settings_button_container,
+        Icon.Resource(
+            R.drawable.ic_settings,
+            ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
+        ),
+        iconTint =
+            Utils.getColorAttrDefaultColor(
+                qsThemedContext,
+                R.attr.onShadeInactiveVariant,
+            ),
+        backgroundColor = R.attr.shadeInactive,
+        onSettingsButtonClicked,
+    )
+}
+
+fun powerButtonViewModel(
+    qsThemedContext: Context,
+    onPowerButtonClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+    return FooterActionsButtonViewModel(
+        id = R.id.pm_lite,
+        Icon.Resource(
+            android.R.drawable.ic_lock_power_off,
+            ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
+        ),
+        iconTint =
+            Utils.getColorAttrDefaultColor(
+                qsThemedContext,
+                R.attr.onShadeActive,
+            ),
+        backgroundColor = R.attr.shadeActive,
+        onPowerButtonClicked,
+    )
 }
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 5a9c0a1..f8e0159 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -23,7 +23,10 @@
 import android.content.res.ColorStateList
 import android.content.res.Configuration
 import android.content.res.Resources.ID_NULL
+import android.graphics.Color
+import android.graphics.PorterDuff
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.RippleDrawable
 import android.os.Trace
 import android.service.quicksettings.Tile
@@ -44,7 +47,6 @@
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.FontSizeUtils
-import com.android.systemui.res.R
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
 import com.android.systemui.plugins.qs.QSIconView
@@ -53,6 +55,7 @@
 import com.android.systemui.plugins.qs.QSTileView
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
+import com.android.systemui.res.R
 import java.util.Objects
 
 private const val TAG = "QSTileViewImpl"
@@ -67,6 +70,7 @@
         private const val LABEL_NAME = "label"
         private const val SECONDARY_LABEL_NAME = "secondaryLabel"
         private const val CHEVRON_NAME = "chevron"
+        private const val OVERLAY_NAME = "overlay"
         const val UNAVAILABLE_ALPHA = 0.3f
         @VisibleForTesting
         internal const val TILE_STATE_RES_PREFIX = "tile_states_"
@@ -97,6 +101,13 @@
     private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive)
     private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled)
 
+    private val overlayColorActive = Utils.applyAlpha(
+        /* alpha= */ 0.11f,
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive))
+    private val overlayColorInactive = Utils.applyAlpha(
+        /* alpha= */ 0.08f,
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive))
+
     private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
     private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
     private val colorLabelUnavailable =
@@ -123,8 +134,13 @@
     protected var showRippleEffect = true
 
     private lateinit var ripple: RippleDrawable
-    private lateinit var colorBackgroundDrawable: Drawable
-    private var paintColor: Int = 0
+    private lateinit var backgroundDrawable: LayerDrawable
+    private lateinit var backgroundBaseDrawable: Drawable
+    private lateinit var backgroundOverlayDrawable: Drawable
+
+    private var backgroundColor: Int = 0
+    private var backgroundOverlayColor: Int = 0
+
     private val singleAnimator: ValueAnimator = ValueAnimator().apply {
         setDuration(QS_ANIM_LENGTH)
         addUpdateListener { animation ->
@@ -134,7 +150,8 @@
                 animation.getAnimatedValue(BACKGROUND_NAME) as Int,
                 animation.getAnimatedValue(LABEL_NAME) as Int,
                 animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int,
-                animation.getAnimatedValue(CHEVRON_NAME) as Int
+                animation.getAnimatedValue(CHEVRON_NAME) as Int,
+                animation.getAnimatedValue(OVERLAY_NAME) as Int,
             )
         }
     }
@@ -263,7 +280,12 @@
 
     fun createTileBackground(): Drawable {
         ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
-        colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background)
+        backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable
+        backgroundBaseDrawable =
+            backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base)
+        backgroundOverlayDrawable =
+            backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay)
+        backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC)
         return ripple
     }
 
@@ -343,10 +365,10 @@
             ripple.also {
                 // In case that the colorBackgroundDrawable was used as the background, make sure
                 // it has the correct callback instead of null
-                colorBackgroundDrawable.callback = it
+                backgroundDrawable.callback = it
             }
         } else {
-            colorBackgroundDrawable
+            backgroundDrawable
         }
     }
 
@@ -512,7 +534,7 @@
                 singleAnimator.setValues(
                         colorValuesHolder(
                                 BACKGROUND_NAME,
-                                paintColor,
+                                backgroundColor,
                                 getBackgroundColorForState(state.state, state.disabledByPolicy)
                         ),
                         colorValuesHolder(
@@ -529,6 +551,11 @@
                                 CHEVRON_NAME,
                                 chevronView.imageTintList?.defaultColor ?: 0,
                                 getChevronColorForState(state.state, state.disabledByPolicy)
+                        ),
+                        colorValuesHolder(
+                                OVERLAY_NAME,
+                                backgroundOverlayColor,
+                                getOverlayColorForState(state.state)
                         )
                     )
                 singleAnimator.start()
@@ -537,7 +564,8 @@
                     getBackgroundColorForState(state.state, state.disabledByPolicy),
                     getLabelColorForState(state.state, state.disabledByPolicy),
                     getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
-                    getChevronColorForState(state.state, state.disabledByPolicy)
+                    getChevronColorForState(state.state, state.disabledByPolicy),
+                    getOverlayColorForState(state.state)
                 )
             }
         }
@@ -555,17 +583,19 @@
         backgroundColor: Int,
         labelColor: Int,
         secondaryLabelColor: Int,
-        chevronColor: Int
+        chevronColor: Int,
+        overlayColor: Int,
     ) {
         setColor(backgroundColor)
         setLabelColor(labelColor)
         setSecondaryLabelColor(secondaryLabelColor)
         setChevronColor(chevronColor)
+        setOverlayColor(overlayColor)
     }
 
     private fun setColor(color: Int) {
-        colorBackgroundDrawable.mutate().setTint(color)
-        paintColor = color
+        backgroundBaseDrawable.mutate().setTint(color)
+        backgroundColor = color
     }
 
     private fun setLabelColor(color: Int) {
@@ -580,6 +610,11 @@
         chevronView.imageTintList = ColorStateList.valueOf(color)
     }
 
+    private fun setOverlayColor(overlayColor: Int) {
+        backgroundOverlayDrawable.setTint(overlayColor)
+        backgroundOverlayColor = overlayColor
+    }
+
     private fun loadSideViewDrawableIfNecessary(state: QSTile.State) {
         if (state.sideViewCustomDrawable != null) {
             customDrawableView.setImageDrawable(state.sideViewCustomDrawable)
@@ -654,9 +689,17 @@
     private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int =
             getSecondaryLabelColorForState(state, disabledByPolicy)
 
+    private fun getOverlayColorForState(state: Int): Int {
+        return when (state) {
+            Tile.STATE_ACTIVE -> overlayColorActive
+            Tile.STATE_INACTIVE -> overlayColorInactive
+            else -> Color.TRANSPARENT
+        }
+    }
+
     @VisibleForTesting
     internal fun getCurrentColors(): List<Int> = listOf(
-            paintColor,
+            backgroundColor,
             label.currentTextColor,
             secondaryLabel.currentTextColor,
             chevronView.imageTintList?.defaultColor ?: 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index fb71cef..17251c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -49,6 +48,7 @@
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
@@ -56,8 +56,6 @@
 
 import javax.inject.Inject;
 
-
-
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
 
@@ -90,8 +88,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
 
-        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
-                userTracker.getUserId()) {
+        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 // mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 18d472b..426aa55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -29,7 +29,6 @@
 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.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -38,9 +37,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 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.BatteryController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -53,7 +53,7 @@
 
     private final BatteryController mBatteryController;
     @VisibleForTesting
-    protected final SettingObserver mSetting;
+    protected final UserSettingObserver mSetting;
 
     private int mLevel;
     private boolean mPowerSave;
@@ -79,7 +79,7 @@
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
         int currentUser = host.getUserContext().getUserId();
-        mSetting = new SettingObserver(
+        mSetting = new UserSettingObserver(
                 secureSettings,
                 mHandler,
                 Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index d862f56..18d2f30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -39,7 +39,6 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -53,6 +52,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
 import java.util.List;
@@ -198,6 +198,7 @@
         }
 
         state.expandedAccessibilityClassName = Switch.class.getName();
+        state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5c6e902..a698208 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,6 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
@@ -53,12 +52,13 @@
 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.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -85,10 +85,9 @@
     private final NetworkController mNetworkController;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final Callback mCallback = new Callback();
-    private final WifiInteractor mWifiInteractor;
     private final TileJavaAdapter mJavaAdapter;
     private final FeatureFlags mFeatureFlags;
-    private boolean mWifiConnected;
+    private boolean mCastTransportAllowed;
     private boolean mHotspotConnected;
 
     @Inject
@@ -107,7 +106,7 @@
             NetworkController networkController,
             HotspotController hotspotController,
             DialogLaunchAnimator dialogLaunchAnimator,
-            WifiInteractor wifiInteractor,
+            ConnectivityRepository connectivityRepository,
             TileJavaAdapter javaAdapter,
             FeatureFlags featureFlags
     ) {
@@ -117,7 +116,6 @@
         mKeyguard = keyguardStateController;
         mNetworkController = networkController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mWifiInteractor = wifiInteractor;
         mJavaAdapter = javaAdapter;
         mFeatureFlags = featureFlags;
         mController.observe(this, mCallback);
@@ -125,7 +123,11 @@
         if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
             mNetworkController.observe(this, mSignalCallback);
         } else {
-            mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+            mJavaAdapter.bind(
+                    this,
+                    connectivityRepository.getDefaultConnections(),
+                    mNetworkModelConsumer
+            );
         }
         hotspotController.observe(this, mHotspotCallback);
     }
@@ -282,7 +284,7 @@
         }
         state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
                 : R.drawable.ic_cast);
-        if (canCastToWifi() || state.value) {
+        if (canCastToNetwork() || state.value) {
             state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
             if (!state.value) {
                 state.secondaryLabel = "";
@@ -291,7 +293,7 @@
             state.forceExpandIcon = willPopDialog();
         } else {
             state.state = Tile.STATE_UNAVAILABLE;
-            String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
+            String noWifi = mContext.getString(R.string.quick_settings_cast_no_network);
             state.secondaryLabel = noWifi;
             state.forceExpandIcon = false;
         }
@@ -308,13 +310,13 @@
                 : mContext.getString(R.string.quick_settings_cast_device_default_name);
     }
 
-    private boolean canCastToWifi() {
-        return mWifiConnected || mHotspotConnected;
+    private boolean canCastToNetwork() {
+        return mCastTransportAllowed || mHotspotConnected;
     }
 
-    private void setWifiConnected(boolean connected) {
-        if (connected != mWifiConnected) {
-            mWifiConnected = connected;
+    private void setCastTransportAllowed(boolean connected) {
+        if (connected != mCastTransportAllowed) {
+            mCastTransportAllowed = connected;
             // Hotspot is not connected, so changes here should update
             if (!mHotspotConnected) {
                 refreshState();
@@ -326,14 +328,17 @@
         if (connected != mHotspotConnected) {
             mHotspotConnected = connected;
             // Wifi is not connected, so changes here should update
-            if (!mWifiConnected) {
+            if (!mCastTransportAllowed) {
                 refreshState();
             }
         }
     }
 
-    private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
-        setWifiConnected(model instanceof WifiNetworkModel.Active);
+    private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> {
+        boolean isWifiDefault = model.getWifi().isDefault();
+        boolean isEthernetDefault = model.getEthernet().isDefault();
+        boolean hasCellularTransport = model.getMobile().isDefault();
+        setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport);
     };
 
     private final SignalCallback mSignalCallback = new SignalCallback() {
@@ -342,7 +347,7 @@
                     // statusIcon.visible has the connected status information
                     boolean enabledAndConnected = indicators.enabled
                             && (indicators.qsIcon != null && indicators.qsIcon.visible);
-                    setWifiConnected(enabledAndConnected);
+                    setCastTransportAllowed(enabledAndConnected);
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index ee57b2b..c8adbfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -28,8 +28,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
-import com.android.systemui.res.R.drawable;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -38,9 +36,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -51,8 +50,8 @@
 
     public static final String TILE_SPEC = "color_correction";
 
-    private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction);
-    private final SettingObserver mSetting;
+    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_color_correction);
+    private final UserSettingObserver mSetting;
 
     @Inject
     public ColorCorrectionTile(
@@ -71,7 +70,7 @@
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
-        mSetting = new SettingObserver(secureSettings, mHandler,
+        mSetting = new UserSettingObserver(secureSettings, mHandler,
                 Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 993ada6..c34a584 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -29,8 +29,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
-import com.android.systemui.res.R.drawable;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -39,9 +37,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -51,7 +50,7 @@
 public class ColorInversionTile extends QSTileImpl<BooleanState> {
 
     public static final String TILE_SPEC = "inversion";
-    private final SettingObserver mSetting;
+    private final UserSettingObserver mSetting;
 
     @Inject
     public ColorInversionTile(
@@ -70,7 +69,7 @@
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
-        mSetting = new SettingObserver(secureSettings, mHandler,
+        mSetting = new UserSettingObserver(secureSettings, mHandler,
                 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
@@ -126,8 +125,8 @@
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_inversion_label);
         state.icon = ResourceIcon.get(state.value
-                ? drawable.qs_invert_colors_icon_on
-                : drawable.qs_invert_colors_icon_off);
+                ? R.drawable.qs_invert_colors_icon_on
+                : R.drawable.qs_invert_colors_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 0617b30..f6518d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -45,7 +45,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.notification.EnableZenModeDialog;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -56,10 +55,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.settings.SecureSettings;
@@ -81,7 +81,7 @@
 
     private final ZenModeController mController;
     private final SharedPreferences mSharedPreferences;
-    private final SettingObserver mSettingZenDuration;
+    private final UserSettingObserver mSettingZenDuration;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
 
@@ -109,7 +109,7 @@
         mSharedPreferences = sharedPreferences;
         mController.observe(getLifecycle(), mZenCallback);
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
+        mSettingZenDuration = new UserSettingObserver(secureSettings, mUiHandler,
                 Settings.Secure.ZEN_DURATION, getHost().getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index a08b3fc..4f0a63b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -38,7 +38,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -49,9 +48,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -69,8 +69,8 @@
     private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked);
     private final IDreamManager mDreamManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final SettingObserver mEnabledSettingObserver;
-    private final SettingObserver mDreamSettingObserver;
+    private final UserSettingObserver mEnabledSettingObserver;
+    private final UserSettingObserver mDreamSettingObserver;
     private final UserTracker mUserTracker;
     private final boolean mDreamSupported;
     private final boolean mDreamOnlyEnabledForDockUser;
@@ -111,14 +111,14 @@
                 statusBarStateController, activityStarter, qsLogger);
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
-        mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
+        mEnabledSettingObserver = new UserSettingObserver(secureSettings, mHandler,
                 Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 refreshState();
             }
         };
-        mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
+        mDreamSettingObserver = new UserSettingObserver(secureSettings, mHandler,
                 Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index 78af976..b08e6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -28,7 +28,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -37,9 +36,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -53,7 +53,7 @@
 
     private final Icon mIcon = ResourceIcon.get(
             com.android.internal.R.drawable.ic_qs_one_handed_mode);
-    private final SettingObserver mSetting;
+    private final UserSettingObserver mSetting;
 
     @Inject
     public OneHandedModeTile(
@@ -70,7 +70,7 @@
             SecureSettings secureSettings) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
-        mSetting = new SettingObserver(secureSettings, mHandler,
+        mSetting = new UserSettingObserver(secureSettings, mHandler,
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 5a95004..f1d8f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -45,9 +44,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 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.BatteryController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
@@ -67,7 +67,7 @@
     private final RotationLockController mController;
     private final SensorPrivacyManager mPrivacyManager;
     private final BatteryController mBatteryController;
-    private final SettingObserver mSetting;
+    private final UserSettingObserver mSetting;
     private final boolean mAllowRotationResolver;
 
     @Inject
@@ -93,7 +93,7 @@
         mPrivacyManager = privacyManager;
         mBatteryController = batteryController;
         int currentUser = host.getUserContext().getUserId();
-        mSetting = new SettingObserver(
+        mSetting = new UserSettingObserver(
                 secureSettings,
                 mHandler,
                 Secure.CAMERA_AUTOROTATE,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 959afd8..f37f58d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -30,12 +30,12 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile;
@@ -45,7 +45,9 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -69,6 +71,8 @@
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final FeatureFlags mFlags;
     private final PanelInteractor mPanelInteractor;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    private final UserContextProvider mUserContextProvider;
 
     private long mMillisUntilFinished = 0;
 
@@ -88,7 +92,9 @@
             KeyguardDismissUtil keyguardDismissUtil,
             KeyguardStateController keyguardStateController,
             DialogLaunchAnimator dialogLaunchAnimator,
-            PanelInteractor panelInteractor
+            PanelInteractor panelInteractor,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+            UserContextProvider userContextProvider
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -99,6 +105,8 @@
         mKeyguardStateController = keyguardStateController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mPanelInteractor = panelInteractor;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+        mUserContextProvider = userContextProvider;
     }
 
     @Override
@@ -190,6 +198,10 @@
             } else {
                 dialog.show();
             }
+
+            int uid = mUserContextProvider.getUserContext().getUserId();
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
+
             return false;
         };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 1b0d5f9..2f8fe42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -32,7 +32,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +42,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.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -55,7 +55,7 @@
     private final KeyguardStateController mKeyguard;
     protected IndividualSensorPrivacyController mSensorPrivacyController;
 
-    private final SafetyCenterManager mSafetyCenterManager;
+    private final Boolean mIsSafetyCenterEnabled;
 
     /**
      * @return Id of the sensor that will be toggled
@@ -89,7 +89,7 @@
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
-        mSafetyCenterManager = safetyCenterManager;
+        mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
         mSensorPrivacyController.observe(getLifecycle(), this);
     }
 
@@ -138,7 +138,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        if (mSafetyCenterManager.isSafetyCenterEnabled()) {
+        if (mIsSafetyCenterEnabled) {
             return new Intent(Settings.ACTION_PRIVACY_CONTROLS);
         } else {
             return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 9d10072..905d8ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.base.actions
 
+import android.app.PendingIntent
 import android.content.Intent
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
@@ -29,7 +30,7 @@
  * dismissing and tile from-view animations.
  */
 @SysUISingleton
-class QSTileIntentUserActionHandler
+class QSTileIntentUserInputHandler
 @Inject
 constructor(private val activityStarter: ActivityStarter) {
 
@@ -43,4 +44,19 @@
             }
         activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
     }
+
+    // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
+    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,
+                )
+            }
+        activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt
new file mode 100644
index 0000000..4f25d3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+/** Event that triggers data update */
+sealed interface DataUpdateTrigger {
+    /**
+     * State update is requested in a response to a user action.
+     * - [action] is the action that happened
+     * - [tileData] is the data state of the tile when that action took place
+     */
+    class UserInput<T>(val input: QSTileInput<T>) : DataUpdateTrigger
+
+    /** Force update current state. This is passed when the view needs a new state to show */
+    data object ForceUpdate : DataUpdateTrigger
+
+    /** The data is requested loaded for the first time */
+    data object InitialRequest : DataUpdateTrigger
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index 056f967..d1f8945 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.base.interactor
 
 import android.content.Context
+import android.os.UserHandle
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
 import com.android.settingslib.RestrictedLockUtils
@@ -32,7 +33,7 @@
 
 /**
  * Provides restrictions data for the tiles. This is used in
- * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is
+ * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl] to determine if the tile is
  * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy].
  */
 interface DisabledByPolicyInteractor {
@@ -41,7 +42,7 @@
      * Checks if the tile is restricted by the policy for a specific user. Pass the result to the
      * [handlePolicyResult] to let the user know that the tile is disable by the admin.
      */
-    suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult
+    suspend fun isDisabled(user: UserHandle, userRestriction: String?): PolicyResult
 
     /**
      * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this
@@ -75,14 +76,14 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : DisabledByPolicyInteractor {
 
-    override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult =
+    override suspend fun isDisabled(user: UserHandle, userRestriction: String?): PolicyResult =
         withContext(backgroundDispatcher) {
             val admin: EnforcedAdmin =
-                restrictedLockProxy.getEnforcedAdmin(userId, userRestriction)
+                restrictedLockProxy.getEnforcedAdmin(user.identifier, userRestriction)
                     ?: return@withContext PolicyResult.TileEnabled
 
             return@withContext if (
-                !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction)
+                !restrictedLockProxy.hasBaseUserRestriction(user.identifier, userRestriction)
             ) {
                 PolicyResult.TileDisabled(admin)
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
index 7a22e3c..9752fea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs.tiles.base.interactor
 
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import android.os.UserHandle
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 
@@ -29,14 +29,19 @@
 interface QSTileDataInteractor<DATA_TYPE> {
 
     /**
-     * Returns the data to be mapped to [QSTileState]. Make sure to start the flow [Flow.onStart]
-     * with the current state to update the tile as soon as possible.
+     * Returns a data flow scoped to the user. This means the subscription will live when the tile
+     * is listened for the [user]. It's cancelled when the tile is not listened or the user changes.
+     *
+     * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
+     * as possible.
      */
-    fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<DATA_TYPE>
+    fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>
 
     /**
-     * Returns tile availability - whether this device currently supports this tile. Make sure to
-     * start the flow [Flow.onStart] with the current state to update the tile as soon as possible.
+     * Returns tile availability - whether this device currently supports this tile.
+     *
+     * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
+     * as possible.
      */
-    fun availability(): Flow<Boolean>
+    fun availability(user: UserHandle): Flow<Boolean>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
index 0aa6b0b..77ff609 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
@@ -16,7 +16,12 @@
 
 package com.android.systemui.qs.tiles.base.interactor
 
-data class QSTileDataRequest(
-    val userId: Int,
-    val trigger: StateUpdateTrigger,
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+
+/** @see QSTileUserActionInteractor.handleInput */
+data class QSTileInput<T>(
+    val user: UserHandle,
+    val action: QSTileUserAction,
+    val data: T,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 14fc639..09d7a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.qs.tiles.base.interactor
 
 import android.annotation.WorkerThread
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 
 interface QSTileUserActionInteractor<DATA_TYPE> {
-
     /**
-     * Processes user input based on [userAction] and [currentData]. It's safe to run long running
-     * computations inside this function in this.
+     * Processes user input based on [QSTileInput.userId], [QSTileInput.action], and
+     * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to
+     * [QSTileDataInteractor] to get [QSTileInput.data].
+     *
+     * It's safe to run long running computations inside this function in this.
      */
-    @WorkerThread suspend fun handleInput(userAction: QSTileUserAction, currentData: DATA_TYPE)
+    @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
deleted file mode 100644
index ffe38dd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles.base.interactor
-
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
-
-sealed interface StateUpdateTrigger {
-    class UserAction<T>(val action: QSTileUserAction, val tileState: QSTileState, val tileData: T) :
-        StateUpdateTrigger
-    data object ForceUpdate : StateUpdateTrigger
-    data object InitialRequest : StateUpdateTrigger
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 70a683b..4dc1c82 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -19,24 +19,23 @@
 import androidx.annotation.GuardedBy
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.QSTilesDefaultLog
 import com.android.systemui.log.dagger.QSTilesLogBuffers
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
-import javax.inject.Provider
 
 @SysUISingleton
 class QSTileLogger
 @Inject
 constructor(
     @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
-    @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+    private val factory: LogBufferFactory,
     private val mStatusBarStateController: StatusBarStateController,
 ) {
     @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
@@ -129,10 +128,21 @@
             )
     }
 
+    fun logForceUpdate(tileSpec: TileSpec) {
+        tileSpec
+            .getLogBuffer()
+            .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data force update" })
+    }
+
+    fun logInitialRequest(tileSpec: TileSpec) {
+        tileSpec
+            .getLogBuffer()
+            .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data initial update" })
+    }
+
     /** Tracks state changes based on the data and trigger event. */
     fun <T> logStateUpdate(
         tileSpec: TileSpec,
-        trigger: StateUpdateTrigger,
         tileState: QSTileState,
         data: T,
     ) {
@@ -142,11 +152,10 @@
                 tileSpec.getLogTag(),
                 LogLevel.DEBUG,
                 {
-                    str1 = trigger.toLogString()
-                    str2 = tileState.toLogString()
-                    str3 = data.toString().take(DATA_MAX_LENGTH)
+                    str1 = tileState.toLogString()
+                    str2 = data.toString().take(DATA_MAX_LENGTH)
                 },
-                { "tile state update: trigger=$str1, state=$str2, data=$str3" }
+                { "tile state update: state=$str1, data=$str2" }
             )
     }
 
@@ -154,14 +163,20 @@
 
     private fun TileSpec.getLogBuffer(): LogBuffer =
         synchronized(logBufferCache) {
-            logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+            logBufferCache.getOrPut(this) {
+                factory.create(
+                    "QSTileLog_${this.getLogTag()}",
+                    BUFFER_MAX_SIZE /* maxSize */,
+                    false /* systrace */
+                )
+            }
         }
 
-    private fun StateUpdateTrigger.toLogString(): String =
+    private fun DataUpdateTrigger.toLogString(): String =
         when (this) {
-            is StateUpdateTrigger.ForceUpdate -> "force"
-            is StateUpdateTrigger.InitialRequest -> "init"
-            is StateUpdateTrigger.UserAction<*> -> action.toLogString()
+            is DataUpdateTrigger.ForceUpdate -> "force"
+            is DataUpdateTrigger.InitialRequest -> "init"
+            is DataUpdateTrigger.UserInput<*> -> input.action.toLogString()
         }
 
     private fun QSTileUserAction.toLogString(): String =
@@ -185,5 +200,6 @@
     private companion object {
         const val TAG_FORMAT_PREFIX = "QSLog"
         const val DATA_MAX_LENGTH = 50
+        const val BUFFER_MAX_SIZE = 25
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
deleted file mode 100644
index 2114751..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles.base.viewmodel
-
-import androidx.annotation.CallSuper
-import androidx.annotation.VisibleForTesting
-import com.android.internal.util.Preconditions
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
-import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
-import com.android.systemui.qs.tiles.base.logging.QSTileLogger
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
-import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
-import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
-import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
-import com.android.systemui.util.kotlin.sample
-import com.android.systemui.util.kotlin.throttle
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Provides a hassle-free way to implement new tiles according to current System UI architecture
- * standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to
- * [QSTileLifecycle.ALIVE] state.
- *
- * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
- */
-class BaseQSTileViewModel<DATA_TYPE>
-@VisibleForTesting
-constructor(
-    override val config: QSTileConfig,
-    private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
-    private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
-    private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
-    private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
-    private val falsingManager: FalsingManager,
-    private val qsTileAnalytics: QSTileAnalytics,
-    private val qsTileLogger: QSTileLogger,
-    private val backgroundDispatcher: CoroutineDispatcher,
-    private val tileScope: CoroutineScope,
-) : QSTileViewModel {
-
-    @AssistedInject
-    constructor(
-        @Assisted config: QSTileConfig,
-        @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
-        @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
-        @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
-        disabledByPolicyInteractor: DisabledByPolicyInteractor,
-        falsingManager: FalsingManager,
-        qsTileAnalytics: QSTileAnalytics,
-        qsTileLogger: QSTileLogger,
-        @Background backgroundDispatcher: CoroutineDispatcher,
-    ) : this(
-        config,
-        userActionInteractor,
-        tileDataInteractor,
-        mapper,
-        disabledByPolicyInteractor,
-        falsingManager,
-        qsTileAnalytics,
-        qsTileLogger,
-        backgroundDispatcher,
-        CoroutineScope(SupervisorJob())
-    )
-
-    private val userInputs: MutableSharedFlow<QSTileUserAction> =
-        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
-    private val userIds: MutableSharedFlow<Int> =
-        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
-    private val forceUpdates: MutableSharedFlow<Unit> =
-        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
-    private val spec
-        get() = config.tileSpec
-
-    private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>>
-
-    override lateinit var state: SharedFlow<QSTileState>
-    override val isAvailable: StateFlow<Boolean> =
-        tileDataInteractor
-            .availability()
-            .flowOn(backgroundDispatcher)
-            .stateIn(
-                tileScope,
-                SharingStarted.WhileSubscribed(),
-                true,
-            )
-
-    private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD
-
-    @CallSuper
-    override fun forceUpdate() {
-        Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
-        forceUpdates.tryEmit(Unit)
-    }
-
-    @CallSuper
-    override fun onUserIdChanged(userId: Int) {
-        Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
-        userIds.tryEmit(userId)
-    }
-
-    @CallSuper
-    override fun onActionPerformed(userAction: QSTileUserAction) {
-        Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
-
-        qsTileLogger.logUserAction(
-            userAction,
-            spec,
-            tileData.replayCache.isNotEmpty(),
-            state.replayCache.isNotEmpty()
-        )
-        userInputs.tryEmit(userAction)
-    }
-
-    @CallSuper
-    override fun onLifecycle(lifecycle: QSTileLifecycle) {
-        when (lifecycle) {
-            QSTileLifecycle.ALIVE -> {
-                Preconditions.checkState(currentLifeState == QSTileLifecycle.DEAD)
-                tileData = createTileDataFlow()
-                state =
-                    tileData
-                        // TODO(b/299908705): log data and corresponding tile state
-                        .map { dataWithTrigger ->
-                            mapper.map(config, dataWithTrigger.data).also { state ->
-                                qsTileLogger.logStateUpdate(
-                                    spec,
-                                    dataWithTrigger.trigger,
-                                    state,
-                                    dataWithTrigger.data
-                                )
-                            }
-                        }
-                        .flowOn(backgroundDispatcher)
-                        .shareIn(
-                            tileScope,
-                            SharingStarted.WhileSubscribed(),
-                            replay = 1,
-                        )
-            }
-            QSTileLifecycle.DEAD -> {
-                Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
-                tileScope.coroutineContext.cancelChildren()
-            }
-        }
-        currentLifeState = lifecycle
-    }
-
-    private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> =
-        userIds
-            .flatMapLatest { userId ->
-                merge(
-                        userInputFlow(userId),
-                        forceUpdates.map { StateUpdateTrigger.ForceUpdate },
-                    )
-                    .onStart { emit(StateUpdateTrigger.InitialRequest) }
-                    .map { trigger -> QSTileDataRequest(userId, trigger) }
-            }
-            .flatMapLatest { request ->
-                // 1) get an updated data source
-                // 2) process user input, possibly triggering new data to be emitted
-                // This handles the case when the data isn't buffered in the interactor
-                // TODO(b/299908705): Log events that trigger data flow to update
-                val dataFlow = tileDataInteractor.tileData(request)
-                if (request.trigger is StateUpdateTrigger.UserAction<*>) {
-                    userActionInteractor.handleInput(
-                        request.trigger.action,
-                        request.trigger.tileData as DATA_TYPE,
-                    )
-                }
-                dataFlow.map { DataWithTrigger(it, request.trigger) }
-            }
-            .flowOn(backgroundDispatcher)
-            .shareIn(
-                tileScope,
-                SharingStarted.WhileSubscribed(),
-                replay = 1, // we only care about the most recent value
-            )
-
-    private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> {
-        data class StateWithData<T>(val state: QSTileState, val data: T)
-
-        return when (config.policy) {
-                is QSTilePolicy.NoRestrictions -> userInputs
-                is QSTilePolicy.Restricted ->
-                    userInputs.filter { action ->
-                        val result =
-                            disabledByPolicyInteractor.isDisabled(
-                                userId,
-                                config.policy.userRestriction
-                            )
-                        !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
-                            if (isDisabled) {
-                                qsTileLogger.logUserActionRejectedByPolicy(action, spec)
-                            }
-                        }
-                    }
-            }
-            .filter { action ->
-                val isFalseAction =
-                    when (action) {
-                        is QSTileUserAction.Click ->
-                            falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
-                        is QSTileUserAction.LongClick ->
-                            falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
-                    }
-                if (isFalseAction) {
-                    qsTileLogger.logUserActionRejectedByFalsing(action, spec)
-                }
-                !isFalseAction
-            }
-            .throttle(500)
-            // Skip the input until there is some data
-            .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
-                input,
-                stateWithData ->
-                StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also {
-                    qsTileLogger.logUserActionPipeline(
-                        spec,
-                        it.action,
-                        stateWithData.state,
-                        stateWithData.data
-                    )
-                    qsTileAnalytics.trackUserAction(config, it.action)
-                }
-            }
-    }
-
-    private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger)
-
-    interface Factory<T> {
-
-        /**
-         * @param config contains all the static information (like TileSpec) about the tile.
-         * @param userActionInteractor encapsulates user input processing logic. Use it to start
-         *   activities, show dialogs or otherwise update the tile state.
-         * @param tileDataInteractor provides [DATA_TYPE] and its availability.
-         * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View
-         *   layer. It's called in [backgroundDispatcher], so it's safe to perform long running
-         *   operations there.
-         */
-        fun create(
-            config: QSTileConfig,
-            userActionInteractor: QSTileUserActionInteractor<T>,
-            tileDataInteractor: QSTileDataInteractor<T>,
-            mapper: QSTileDataToStateMapper<T>,
-        ): BaseQSTileViewModel<T>
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
new file mode 100644
index 0000000..736f7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.di.QSTileComponent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Factory to create an appropriate [QSTileViewModelImpl] instance depending on your circumstances.
+ *
+ * @see [QSTileViewModelFactory.Component]
+ * @see [QSTileViewModelFactory.Static]
+ */
+sealed interface QSTileViewModelFactory<T> {
+
+    /**
+     * This factory allows you to pass an instance of [QSTileComponent] to a view model effectively
+     * binding them together. This achieves a DI scope that lives along the instance of
+     * [QSTileViewModelImpl].
+     */
+    class Component<T>
+    @Inject
+    constructor(
+        private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
+        private val userRepository: UserRepository,
+        private val falsingManager: FalsingManager,
+        private val qsTileAnalytics: QSTileAnalytics,
+        private val qsTileLogger: QSTileLogger,
+        private val qsTileConfigProvider: QSTileConfigProvider,
+        private val systemClock: SystemClock,
+        @Background private val backgroundDispatcher: CoroutineDispatcher,
+    ) : QSTileViewModelFactory<T> {
+
+        /**
+         * Creates [QSTileViewModelImpl] based on the interactors obtained from [component].
+         * Reference of that [component] is then stored along the view model.
+         */
+        fun create(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> =
+            QSTileViewModelImpl(
+                qsTileConfigProvider.getConfig(tileSpec.spec),
+                component::userActionInteractor,
+                component::dataInteractor,
+                component::dataToStateMapper,
+                disabledByPolicyInteractor,
+                userRepository,
+                falsingManager,
+                qsTileAnalytics,
+                qsTileLogger,
+                systemClock,
+                backgroundDispatcher,
+            )
+    }
+
+    /**
+     * This factory passes by necessary implementations to the [QSTileViewModelImpl]. This is a
+     * default choice for most of the tiles.
+     */
+    class Static<T>
+    @Inject
+    constructor(
+        private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
+        private val userRepository: UserRepository,
+        private val falsingManager: FalsingManager,
+        private val qsTileAnalytics: QSTileAnalytics,
+        private val qsTileLogger: QSTileLogger,
+        private val qsTileConfigProvider: QSTileConfigProvider,
+        private val systemClock: SystemClock,
+        @Background private val backgroundDispatcher: CoroutineDispatcher,
+    ) : QSTileViewModelFactory<T> {
+
+        /**
+         * @param tileSpec of the created tile.
+         * @param userActionInteractor encapsulates user input processing logic. Use it to start
+         *   activities, show dialogs or otherwise update the tile state.
+         * @param tileDataInteractor provides [DATA_TYPE] and its availability.
+         * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View
+         *   layer. It's called in [backgroundDispatcher], so it's safe to perform long running
+         *   operations there.
+         */
+        fun create(
+            tileSpec: TileSpec,
+            userActionInteractor: QSTileUserActionInteractor<T>,
+            tileDataInteractor: QSTileDataInteractor<T>,
+            mapper: QSTileDataToStateMapper<T>,
+        ): QSTileViewModelImpl<T> =
+            QSTileViewModelImpl(
+                qsTileConfigProvider.getConfig(tileSpec.spec),
+                { userActionInteractor },
+                { tileDataInteractor },
+                { mapper },
+                disabledByPolicyInteractor,
+                userRepository,
+                falsingManager,
+                qsTileAnalytics,
+                qsTileLogger,
+                systemClock,
+                backgroundDispatcher,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
new file mode 100644
index 0000000..0bee48f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -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 com.android.systemui.qs.tiles.base.viewmodel
+
+import android.os.UserHandle
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.throttle
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.cancellable
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides a hassle-free way to implement new tiles according to current System UI architecture
+ * standards. This ViewModel is cheap to instantiate and does nothing until its [state] is listened.
+ *
+ * Don't use this constructor directly. Instead, inject [QSTileViewModelFactory] to create a new
+ * instance of this class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSTileViewModelImpl<DATA_TYPE>(
+    override val config: QSTileConfig,
+    private val userActionInteractor: () -> QSTileUserActionInteractor<DATA_TYPE>,
+    private val tileDataInteractor: () -> QSTileDataInteractor<DATA_TYPE>,
+    private val mapper: () -> QSTileDataToStateMapper<DATA_TYPE>,
+    private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
+    userRepository: UserRepository,
+    private val falsingManager: FalsingManager,
+    private val qsTileAnalytics: QSTileAnalytics,
+    private val qsTileLogger: QSTileLogger,
+    private val systemClock: SystemClock,
+    private val backgroundDispatcher: CoroutineDispatcher,
+    private val tileScope: CoroutineScope = CoroutineScope(SupervisorJob()),
+) : QSTileViewModel {
+
+    private val users: MutableStateFlow<UserHandle> =
+        MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
+    private val userInputs: MutableSharedFlow<QSTileUserAction> =
+        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    private val forceUpdates: MutableSharedFlow<Unit> =
+        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    private val spec
+        get() = config.tileSpec
+
+    private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow()
+
+    override val state: SharedFlow<QSTileState> =
+        tileData
+            .map { data ->
+                mapper().map(config, data).also { state ->
+                    qsTileLogger.logStateUpdate(spec, state, data)
+                }
+            }
+            .flowOn(backgroundDispatcher)
+            .shareIn(
+                tileScope,
+                SharingStarted.WhileSubscribed(),
+                replay = 1,
+            )
+    override val isAvailable: StateFlow<Boolean> =
+        users
+            .flatMapLatest { tileDataInteractor().availability(it) }
+            .flowOn(backgroundDispatcher)
+            .stateIn(
+                tileScope,
+                SharingStarted.WhileSubscribed(),
+                true,
+            )
+
+    override fun forceUpdate() {
+        forceUpdates.tryEmit(Unit)
+    }
+
+    override fun onUserChanged(user: UserHandle) {
+        users.tryEmit(user)
+    }
+
+    override fun onActionPerformed(userAction: QSTileUserAction) {
+        qsTileLogger.logUserAction(
+            userAction,
+            spec,
+            tileData.replayCache.isNotEmpty(),
+            state.replayCache.isNotEmpty()
+        )
+        userInputs.tryEmit(userAction)
+    }
+
+    override fun destroy() {
+        tileScope.cancel()
+    }
+
+    private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+        users
+            .flatMapLatest { user ->
+                val updateTriggers =
+                    merge(
+                            userInputFlow(user),
+                            forceUpdates
+                                .map { DataUpdateTrigger.ForceUpdate }
+                                .onEach { qsTileLogger.logForceUpdate(spec) },
+                        )
+                        .onStart {
+                            emit(DataUpdateTrigger.InitialRequest)
+                            qsTileLogger.logInitialRequest(spec)
+                        }
+                tileDataInteractor()
+                    .tileData(user, updateTriggers)
+                    .cancellable()
+                    .flowOn(backgroundDispatcher)
+            }
+            .shareIn(
+                tileScope,
+                SharingStarted.WhileSubscribed(),
+                replay = 1, // we only care about the most recent value
+            )
+
+    /**
+     * Creates a user input flow which:
+     * - filters false inputs with [falsingManager]
+     * - takes care of a tile being disable by policy using [disabledByPolicyInteractor]
+     * - notifies [userActionInteractor] about the action
+     * - logs it accordingly using [qsTileLogger] and [qsTileAnalytics]
+     *
+     * Subscribing to the result flow twice will result in doubling all actions, logs and analytics.
+     */
+    private fun userInputFlow(user: UserHandle): Flow<DataUpdateTrigger> {
+        return userInputs
+            .filterFalseActions()
+            .filterByPolicy(user)
+            .throttle(CLICK_THROTTLE_DURATION, systemClock)
+            // Skip the input until there is some data
+            .mapNotNull { action ->
+                val state: QSTileState = state.replayCache.lastOrNull() ?: return@mapNotNull null
+                val data: DATA_TYPE = tileData.replayCache.lastOrNull() ?: return@mapNotNull null
+                qsTileLogger.logUserActionPipeline(spec, action, state, data)
+                qsTileAnalytics.trackUserAction(config, action)
+
+                DataUpdateTrigger.UserInput(QSTileInput(user, action, data))
+            }
+            .onEach { userActionInteractor().handleInput(it.input) }
+            .flowOn(backgroundDispatcher)
+    }
+
+    private fun Flow<QSTileUserAction>.filterByPolicy(user: UserHandle): Flow<QSTileUserAction> =
+        config.policy.let { policy ->
+            when (policy) {
+                is QSTilePolicy.NoRestrictions -> this@filterByPolicy
+                is QSTilePolicy.Restricted ->
+                    filter { action ->
+                        val result =
+                            disabledByPolicyInteractor.isDisabled(user, policy.userRestriction)
+                        !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
+                            if (isDisabled) {
+                                qsTileLogger.logUserActionRejectedByPolicy(action, spec)
+                            }
+                        }
+                    }
+            }
+        }
+
+    private fun Flow<QSTileUserAction>.filterFalseActions(): Flow<QSTileUserAction> =
+        filter { action ->
+            val isFalseAction =
+                when (action) {
+                    is QSTileUserAction.Click ->
+                        falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+                    is QSTileUserAction.LongClick ->
+                        falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+                }
+            if (isFalseAction) {
+                qsTileLogger.logUserActionRejectedByFalsing(action, spec)
+            }
+            !isFalseAction
+        }
+
+    private companion object {
+        const val CLICK_THROTTLE_DURATION = 200L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index d0809c5..7d7af64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.qs.QSFactory
 import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
 import javax.inject.Inject
@@ -30,15 +30,22 @@
 class NewQSTileFactory
 @Inject
 constructor(
+    qsTileConfigProvider: QSTileConfigProvider,
     private val adapterFactory: QSTileViewModelAdapter.Factory,
     private val tileMap:
         Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
 ) : QSFactory {
 
+    init {
+        for (viewModelTileSpec in tileMap.keys) {
+            // throws an exception when there is no config for a tileSpec of an injected viewModel
+            qsTileConfigProvider.getConfig(viewModelTileSpec)
+        }
+    }
+
     override fun createTile(tileSpec: String): QSTile? =
         tileMap[tileSpec]?.let {
             val tile = it.get()
-            tile.onLifecycle(QSTileLifecycle.ALIVE)
             adapterFactory.create(tile)
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
new file mode 100644
index 0000000..32522ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.di
+
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.Multibinds
+
+/** Module listing subcomponents */
+@Module(
+    subcomponents =
+        [
+            CustomTileComponent::class,
+        ]
+)
+interface QSTilesModule {
+
+    /**
+     * A map of internal QS tile ViewModels. Ensures that this can be injected even if it is empty
+     */
+    @Multibinds fun tileViewModelConfigs(): Map<String, QSTileConfig>
+
+    /**
+     * A map of internal QS tile ViewModels. Ensures that this can be injected even if it is empty
+     */
+    @Multibinds fun tileViewModelMap(): Map<String, QSTileViewModel>
+
+    @Binds fun bindQSTileConfigProvider(impl: QSTileConfigProviderImpl): QSTileConfigProvider
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
index 8957fc3..9c63a30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.dialog.bluetooth
 
+import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothAdapter.STATE_OFF
 import android.bluetooth.BluetoothAdapter.STATE_ON
 import com.android.settingslib.bluetooth.BluetoothCallback
@@ -37,6 +38,7 @@
 @Inject
 constructor(
     private val localBluetoothManager: LocalBluetoothManager?,
+    private val logger: BluetoothTileDialogLogger,
     @Application private val coroutineScope: CoroutineScope,
 ) {
 
@@ -47,6 +49,10 @@
                         override fun onBluetoothStateChanged(bluetoothState: Int) {
                             if (bluetoothState == STATE_ON || bluetoothState == STATE_OFF) {
                                 super.onBluetoothStateChanged(bluetoothState)
+                                logger.logBluetoothState(
+                                    BluetoothStateStage.BLUETOOTH_STATE_CHANGE_RECEIVED,
+                                    BluetoothAdapter.nameForState(bluetoothState)
+                                )
                                 trySendWithFailureLogging(
                                     bluetoothState == STATE_ON,
                                     TAG,
@@ -70,6 +76,10 @@
             if (isBluetoothEnabled != value) {
                 localBluetoothManager?.bluetoothAdapter?.apply {
                     if (value) enable() else disable()
+                    logger.logBluetoothState(
+                        BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET,
+                        value.toString()
+                    )
                 }
             }
         }
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 8ae2dc2..4096226 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
@@ -34,6 +34,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.time.SystemClock
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
@@ -46,7 +47,9 @@
     private val bluetoothToggleInitialValue: Boolean,
     private val subtitleResIdInitialValue: Int,
     private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+    private val systemClock: SystemClock,
     private val uiEventLogger: UiEventLogger,
+    private val logger: BluetoothTileDialogLogger,
     context: Context,
 ) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) {
 
@@ -102,9 +105,12 @@
         showSeeAll: Boolean,
         showPairNewDevice: Boolean
     ) {
-        seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
-        pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
-        deviceItemAdapter.refreshDeviceItemList(deviceItem)
+        val start = systemClock.elapsedRealtime()
+        deviceItemAdapter.refreshDeviceItemList(deviceItem) {
+            seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
+            pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
+            logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start)
+        }
     }
 
     internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) {
@@ -116,6 +122,7 @@
         toggleView.isChecked = bluetoothToggleInitialValue
         toggleView.setOnCheckedChangeListener { _, isChecked ->
             mutableBluetoothStateToggle.value = isChecked
+            logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString())
             uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
         }
     }
@@ -173,8 +180,8 @@
 
         internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
 
-        internal fun refreshDeviceItemList(updated: List<DeviceItem>) {
-            asyncListDiffer.submitList(updated)
+        internal fun refreshDeviceItemList(updated: List<DeviceItem>, callback: () -> Unit) {
+            asyncListDiffer.submitList(updated, callback)
         }
 
         internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
new file mode 100644
index 0000000..5d18dc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog.bluetooth
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.dagger.BluetoothTileDialogLog
+import javax.inject.Inject
+
+private const val TAG = "BluetoothTileDialogLog"
+
+enum class BluetoothStateStage {
+    USER_TOGGLED,
+    BLUETOOTH_STATE_VALUE_SET,
+    BLUETOOTH_STATE_CHANGE_RECEIVED
+}
+
+enum class DeviceFetchTrigger {
+    FIRST_LOAD,
+    BLUETOOTH_STATE_CHANGE_RECEIVED,
+    BLUETOOTH_CALLBACK_RECEIVED
+}
+
+enum class JobStatus {
+    FINISHED,
+    CANCELLED
+}
+
+class BluetoothTileDialogLogger
+@Inject
+constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
+
+    fun logBluetoothState(stage: BluetoothStateStage, state: String) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = stage.toString()
+                str2 = state
+            },
+            { "BluetoothState. stage=$str1 state=$str2" }
+        )
+
+    fun logDeviceClick(address: String, type: DeviceItemType) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = address
+                str2 = type.toString()
+            },
+            { "DeviceClick. address=$str1 type=$str2" }
+        )
+
+    fun logActiveDeviceChanged(address: String?, profileId: Int) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = address
+                int1 = profileId
+            },
+            { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
+        )
+
+    fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = address
+                str2 = state
+                int1 = profileId
+            },
+            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
+        )
+
+    fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = status.toString()
+                str2 = trigger.toString()
+                long1 = duration
+            },
+            { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" }
+        )
+
+    fun logDeviceUiUpdate(duration: Long) =
+        logBuffer.log(TAG, DEBUG, { long1 = duration }, { "DeviceUiUpdate. duration=$long1" })
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
index 2865ad7..86e5dde 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
@@ -28,7 +28,10 @@
     @UiEvent(doc = "Gear icon clicked") DEVICE_GEAR_CLICKED(1497),
     @UiEvent(doc = "Device clicked") DEVICE_CLICKED(1498),
     @UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
-    @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500);
+    @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
+    @UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
+    @UiEvent(doc = "Connected other device clicked to disconnect")
+    CONNECTED_OTHER_DEVICE_DISCONNECT(1508);
 
     override fun getId() = metricId
 }
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 97e1783..f7e0de3 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
@@ -35,10 +35,12 @@
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -53,7 +55,9 @@
     private val bluetoothStateInteractor: BluetoothStateInteractor,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val activityStarter: ActivityStarter,
+    private val systemClock: SystemClock,
     private val uiEventLogger: UiEventLogger,
+    private val logger: BluetoothTileDialogLogger,
     @Application private val coroutineScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
 ) : BluetoothTileDialogCallback {
@@ -90,7 +94,11 @@
                 }
                     ?: dialog!!.show()
                 updateDeviceItemJob?.cancel()
-                updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context) }
+                updateDeviceItemJob = launch {
+                    // Add a slight delay for smoother dialog bounds change
+                    delay(FIRST_LOAD_DELAY_MS)
+                    deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
+                }
 
                 bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
@@ -98,7 +106,10 @@
                         dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it))
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
-                            deviceItemInteractor.updateDeviceItems(context)
+                            deviceItemInteractor.updateDeviceItems(
+                                context,
+                                DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED
+                            )
                         }
                     }
                     .launchIn(this)
@@ -107,13 +118,15 @@
                     .onEach {
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
-                            deviceItemInteractor.updateDeviceItems(context)
+                            deviceItemInteractor.updateDeviceItems(
+                                context,
+                                DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
+                            )
                         }
                     }
                     .launchIn(this)
 
                 deviceItemInteractor.deviceItemUpdate
-                    .filterNotNull()
                     .onEach {
                         dialog!!.onDeviceItemUpdated(
                             it.take(MAX_DEVICE_ITEM_ENTRY),
@@ -140,7 +153,9 @@
                 bluetoothStateInteractor.isBluetoothEnabled,
                 getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled),
                 this@BluetoothTileDialogViewModel,
+                systemClock,
                 uiEventLogger,
+                logger,
                 context
             )
             .apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } }
@@ -190,6 +205,7 @@
 
     companion object {
         private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
+        private const val FIRST_LOAD_DELAY_MS = 500L
         private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
             if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
             else R.string.bt_is_off
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
index 50eaf38..2c8d2a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
@@ -36,6 +36,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 
 enum class DeviceItemType {
+    ACTIVE_MEDIA_BLUETOOTH_DEVICE,
     AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
     CONNECTED_BLUETOOTH_DEVICE,
     SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
index 8c22614..7bb1619 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
@@ -39,12 +39,38 @@
     abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
 }
 
+internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
+    override fun isFilterMatched(
+        cachedDevice: CachedBluetoothDevice,
+        audioManager: AudioManager?
+    ): Boolean {
+        return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
+            BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
+    }
+
+    override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+        return DeviceItem(
+            type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+            cachedBluetoothDevice = cachedDevice,
+            deviceName = cachedDevice.name,
+            connectionSummary = cachedDevice.connectionSummary ?: "",
+            iconWithDescription =
+                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
+                    Pair(p.first, p.second)
+                },
+            background = backgroundOn,
+            isEnabled = !cachedDevice.isBusy,
+        )
+    }
+}
+
 internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
         cachedDevice: CachedBluetoothDevice,
         audioManager: AudioManager?
     ): Boolean {
-        return BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
+        return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
+            BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
     }
 
     // TODO(b/298124674): move create() to the abstract class to reduce duplicate code
@@ -59,7 +85,7 @@
                 BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
                     Pair(p.first, p.second)
                 },
-            background = backgroundOn,
+            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
             isEnabled = !cachedDevice.isBusy,
         )
     }
@@ -84,7 +110,7 @@
                 BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
                     Pair(p.first, p.second)
                 },
-            background = backgroundOn,
+            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
             isEnabled = !cachedDevice.isBusy,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index 14d24f9..76fbf8e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -22,7 +22,6 @@
 import android.media.AudioManager
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.BluetoothCallback
-import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -30,15 +29,17 @@
 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.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.withContext
 
 /** Holds business logic for the Bluetooth Dialog after clicking on the Bluetooth QS tile. */
@@ -50,15 +51,17 @@
     private val audioManager: AudioManager,
     private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(),
     private val localBluetoothManager: LocalBluetoothManager?,
+    private val systemClock: SystemClock,
     private val uiEventLogger: UiEventLogger,
+    private val logger: BluetoothTileDialogLogger,
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
-    private val mutableDeviceItemUpdate: MutableStateFlow<List<DeviceItem>?> =
-        MutableStateFlow(null)
+    private val mutableDeviceItemUpdate: MutableSharedFlow<List<DeviceItem>> =
+        MutableSharedFlow(extraBufferCapacity = 1)
     internal val deviceItemUpdate
-        get() = mutableDeviceItemUpdate.asStateFlow()
+        get() = mutableDeviceItemUpdate.asSharedFlow()
 
     internal val deviceItemUpdateRequest: SharedFlow<Unit> =
         conflatedCallbackFlow {
@@ -69,22 +72,10 @@
                             bluetoothProfile: Int
                         ) {
                             super.onActiveDeviceChanged(activeDevice, bluetoothProfile)
+                            logger.logActiveDeviceChanged(activeDevice?.address, bluetoothProfile)
                             trySendWithFailureLogging(Unit, TAG, "onActiveDeviceChanged")
                         }
 
-                        override fun onConnectionStateChanged(
-                            cachedDevice: CachedBluetoothDevice?,
-                            state: Int
-                        ) {
-                            super.onConnectionStateChanged(cachedDevice, state)
-                            trySendWithFailureLogging(Unit, TAG, "onConnectionStateChanged")
-                        }
-
-                        override fun onDeviceAdded(cachedDevice: CachedBluetoothDevice) {
-                            super.onDeviceAdded(cachedDevice)
-                            trySendWithFailureLogging(Unit, TAG, "onDeviceAdded")
-                        }
-
                         override fun onProfileConnectionStateChanged(
                             cachedDevice: CachedBluetoothDevice,
                             state: Int,
@@ -95,8 +86,24 @@
                                 state,
                                 bluetoothProfile
                             )
+                            logger.logProfileConnectionStateChanged(
+                                cachedDevice.address,
+                                state.toString(),
+                                bluetoothProfile
+                            )
                             trySendWithFailureLogging(Unit, TAG, "onProfileConnectionStateChanged")
                         }
+
+                        override fun onAclConnectionStateChanged(
+                            cachedDevice: CachedBluetoothDevice,
+                            state: Int
+                        ) {
+                            super.onAclConnectionStateChanged(cachedDevice, state)
+                            // Listen only when a device is disconnecting
+                            if (state == 0) {
+                                trySendWithFailureLogging(Unit, TAG, "onAclConnectionStateChanged")
+                            }
+                        }
                     }
                 localBluetoothManager?.eventManager?.registerCallback(listener)
                 awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
@@ -105,6 +112,7 @@
 
     private var deviceItemFactoryList: List<DeviceItemFactory> =
         listOf(
+            ActiveMediaDeviceItemFactory(),
             AvailableMediaDeviceItemFactory(),
             ConnectedDeviceItemFactory(),
             SavedDeviceItemFactory()
@@ -112,23 +120,39 @@
 
     private var displayPriority: List<DeviceItemType> =
         listOf(
+            DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
         )
 
-    internal suspend fun updateDeviceItems(context: Context) {
+    internal suspend fun updateDeviceItems(context: Context, trigger: DeviceFetchTrigger) {
         withContext(backgroundDispatcher) {
-            val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices
-
-            mutableDeviceItemUpdate.value =
+            val start = systemClock.elapsedRealtime()
+            val deviceItems =
                 bluetoothTileDialogRepository.cachedDevices
                     .mapNotNull { cachedDevice ->
                         deviceItemFactoryList
                             .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) }
                             ?.create(context, cachedDevice)
                     }
-                    .sort(displayPriority, mostRecentlyConnectedDevices)
+                    .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
+
+            // Only emit when the job is not cancelled
+            if (isActive) {
+                mutableDeviceItemUpdate.tryEmit(deviceItems)
+                logger.logDeviceFetch(
+                    JobStatus.FINISHED,
+                    trigger,
+                    systemClock.elapsedRealtime() - start
+                )
+            } else {
+                logger.logDeviceFetch(
+                    JobStatus.CANCELLED,
+                    trigger,
+                    systemClock.elapsedRealtime() - start
+                )
+            }
         }
     }
 
@@ -145,17 +169,26 @@
     }
 
     internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
-        when (deviceItem.type) {
-            DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
-                if (!BluetoothUtils.isActiveMediaDevice(deviceItem.cachedBluetoothDevice)) {
-                    deviceItem.cachedBluetoothDevice.setActive()
+        logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
+
+        deviceItem.cachedBluetoothDevice.apply {
+            when (deviceItem.type) {
+                DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
+                    disconnect()
+                    uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
+                }
+                DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
+                    setActive()
                     uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
                 }
-            }
-            DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {}
-            DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
-                deviceItem.cachedBluetoothDevice.connect()
-                uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+                DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
+                    disconnect()
+                    uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT)
+                }
+                DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
+                    connect()
+                    uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt
new file mode 100644
index 0000000..bb5a229
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.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.qs.tiles.impl.custom
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
+
+data class CustomTileData(
+    val user: UserHandle,
+    val componentName: ComponentName,
+    val tile: Tile,
+    val callingAppUid: Int,
+    val isActive: Boolean,
+    val hasPendingBind: Boolean,
+    val shouldShowChevron: Boolean,
+    val defaultTileLabel: CharSequence?,
+    val defaultTileIcon: Icon?,
+    val component: CustomTileBoundComponent,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
new file mode 100644
index 0000000..761274e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@QSTileScope
+class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileData> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<CustomTileData> {
+        TODO("Not yet implemented")
+    }
+
+    override fun availability(user: UserHandle): Flow<Boolean> {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
new file mode 100644
index 0000000..f7bec02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.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.qs.tiles.impl.custom
+
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import javax.inject.Inject
+
+@QSTileScope
+class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileData> {
+
+    override fun map(config: QSTileConfig, data: CustomTileData): QSTileState {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
new file mode 100644
index 0000000..6c1c1a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.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.qs.tiles.impl.custom
+
+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.di.QSTileScope
+import javax.inject.Inject
+
+@QSTileScope
+class CustomTileUserActionInteractor @Inject constructor() :
+    QSTileUserActionInteractor<CustomTileData> {
+
+    override suspend fun handleInput(input: QSTileInput<CustomTileData>) {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
new file mode 100644
index 0000000..01df906
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.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.qs.tiles.impl.custom.di
+
+import com.android.systemui.qs.tiles.impl.di.QSTileComponent
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import dagger.Subcomponent
+
+@QSTileScope
+@Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class])
+interface CustomTileComponent : QSTileComponent<Any> {
+
+    @Subcomponent.Builder
+    interface Builder {
+
+        fun qsTileConfigModule(module: QSTileConfigModule): Builder
+
+        fun build(): CustomTileComponent
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
new file mode 100644
index 0000000..ccff8af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.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.qs.tiles.impl.custom.di
+
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.custom.CustomTileData
+import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper
+import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
+import dagger.Binds
+import dagger.Module
+
+/** Provides bindings for QSTile interfaces */
+@Module(subcomponents = [CustomTileBoundComponent::class])
+interface CustomTileModule {
+
+    @Binds
+    fun bindDataInteractor(
+        dataInteractor: CustomTileInteractor
+    ): QSTileDataInteractor<CustomTileData>
+
+    @Binds
+    fun bindUserActionInteractor(
+        userActionInteractor: CustomTileUserActionInteractor
+    ): QSTileUserActionInteractor<CustomTileData>
+
+    @Binds
+    fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileData>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/QSTileConfigModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/QSTileConfigModule.kt
new file mode 100644
index 0000000..558fb64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/QSTileConfigModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.di
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import dagger.Module
+import dagger.Provides
+
+/**
+ * Provides [QSTileConfig] and [TileSpec]. To be used along in a QS tile scoped component
+ * implementing [com.android.systemui.qs.tiles.impl.di.QSTileComponent]. In that case it makes it
+ * possible to inject config and tile spec associated with the current tile
+ */
+@Module
+class QSTileConfigModule(private val config: QSTileConfig) {
+
+    @Provides fun provideConfig(): QSTileConfig = config
+
+    @Provides fun provideTileSpec(): TileSpec = config.tileSpec
+
+    @Provides
+    fun provideCustomTileSpec(): TileSpec.CustomTileSpec =
+        config.tileSpec as TileSpec.CustomTileSpec
+
+    @Provides
+    fun providePlatformTileSpec(): TileSpec.PlatformTileSpec =
+        config.tileSpec as TileSpec.PlatformTileSpec
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
new file mode 100644
index 0000000..e33b3e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.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.qs.tiles.impl.custom.di.bound
+
+import android.os.UserHandle
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/** @see CustomTileBoundScope */
+@CustomTileBoundScope
+@Subcomponent
+interface CustomTileBoundComponent {
+
+    @Subcomponent.Builder
+    interface Builder {
+        @BindsInstance fun user(@CustomTileUser user: UserHandle): Builder
+        @BindsInstance fun coroutineScope(@CustomTileBoundScope scope: CoroutineScope): Builder
+
+        fun build(): CustomTileBoundComponent
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
index 6575cdd..4a4ba2b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.log.dagger
 
-import javax.inject.Qualifier
+package com.android.systemui.qs.tiles.impl.custom.di.bound
+
+import javax.inject.Scope
 
 /**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
+ * Scope annotation for bound custom tile scope. This scope lives when a particular
+ * [com.android.systemui.qs.external.CustomTile] is listening and bound to the
+ * [android.service.quicksettings.TileService].
  */
-@Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
+@Scope
+annotation class CustomTileBoundScope
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
index 0aa6b0b..efc7431 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.base.interactor
+package com.android.systemui.qs.tiles.impl.custom.di.bound
 
-data class QSTileDataRequest(
-    val userId: Int,
-    val trigger: StateUpdateTrigger,
-)
+import javax.inject.Qualifier
+
+/** User associated with current custom tile binding. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CustomTileUser
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt
new file mode 100644
index 0000000..b3d916a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.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.qs.tiles.impl.di
+
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+
+/**
+ * Base QS tile component. It should be used with [QSTileScope] to create a custom tile scoped
+ * component. Pass this component to
+ * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory.Component].
+ */
+interface QSTileComponent<T> {
+
+    fun dataInteractor(): QSTileDataInteractor<T>
+
+    fun userActionInteractor(): QSTileUserActionInteractor<T>
+
+    fun dataToStateMapper(): QSTileDataToStateMapper<T>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileScope.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileScope.kt
new file mode 100644
index 0000000..a412de3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileScope.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.qs.tiles.impl.di
+
+import javax.inject.Scope
+
+/**
+ * Scope annotation for QS tiles. This scope is created for each tile and is disposed when the tile
+ * is no longer needed (ex. it's removed from QS). So, it lives along the instance of
+ * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl]. This doesn't align with tile
+ * visibility. For example, the tile scope survives shade open/close.
+ */
+@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class QSTileScope
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index 4a3bcae..c4d7dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -16,20 +16,49 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
+import android.content.res.Resources
+import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import com.android.internal.logging.InstanceId
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 data class QSTileConfig(
     val tileSpec: TileSpec,
-    val tileIcon: Icon,
-    @StringRes val tileLabelRes: Int,
+    val uiConfig: QSTileUIConfig,
     val instanceId: InstanceId,
     val metricsSpec: String = tileSpec.spec,
     val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
 )
 
+/**
+ * Static tile icon and label to be used when the fully operational tile isn't needed (ex. in edit
+ * mode). Icon and label are resources to better support config/locale changes.
+ */
+sealed interface QSTileUIConfig {
+
+    val tileIconRes: Int
+        @DrawableRes get
+    val tileLabelRes: Int
+        @StringRes get
+
+    /**
+     * Represents the absence of static UI state. This should be avoided by platform tiles in favour
+     * of [Resource]. Returns [Resources.ID_NULL] for each field.
+     */
+    data object Empty : QSTileUIConfig {
+        override val tileIconRes: Int
+            get() = Resources.ID_NULL
+        override val tileLabelRes: Int
+            get() = Resources.ID_NULL
+    }
+
+    /** Config containing actual icon and label resources. */
+    data class Resource(
+        @StringRes override val tileIconRes: Int,
+        @StringRes override val tileLabelRes: Int,
+    ) : QSTileUIConfig
+}
+
 /** Represents policy restrictions that may be imposed on the tile. */
 sealed interface QSTilePolicy {
     /** Tile has no policy restrictions */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
new file mode 100644
index 0000000..3f3b94e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import com.android.internal.util.Preconditions
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface QSTileConfigProvider {
+
+    /**
+     * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no
+     * config for such [tileSpec].
+     */
+    fun getConfig(tileSpec: String): QSTileConfig
+}
+
+@SysUISingleton
+class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) :
+    QSTileConfigProvider {
+
+    init {
+        for (entry in configs.entries) {
+            val configTileSpec = entry.value.tileSpec.spec
+            val keyTileSpec = entry.key
+            Preconditions.checkArgument(
+                configTileSpec == keyTileSpec,
+                "A wrong config is injected keySpec=$keyTileSpec configSpec=$configTileSpec"
+            )
+        }
+    }
+
+    override fun getConfig(tileSpec: String): QSTileConfig =
+        configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 0ccde74..dc5cccc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -59,7 +59,16 @@
         // This represents a tile that is currently in a disabled state but is still interactable. A
         // disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
         // bluetooth disabled), but is still interactable by the user to modify this state.
-        INACTIVE(Tile.STATE_INACTIVE),
+        INACTIVE(Tile.STATE_INACTIVE);
+
+        companion object {
+            fun valueOf(legacyState: Int): ActivationState =
+                when (legacyState) {
+                    Tile.STATE_ACTIVE -> ACTIVE
+                    Tile.STATE_INACTIVE -> INACTIVE
+                    else -> UNAVAILABLE
+                }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index e5cb7ea..580c421 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
+import android.os.UserHandle
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -29,38 +30,32 @@
  */
 interface QSTileViewModel {
 
-    /**
-     * State of the tile to be shown by the view. It's guaranteed that it's only accessed between
-     * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD].
-     */
+    /** State of the tile to be shown by the view. */
     val state: SharedFlow<QSTileState>
 
     val config: QSTileConfig
 
-    /**
-     * Specifies whether this device currently supports this tile. This might be called outside of
-     * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds (for example in Edit Mode).
-     */
+    /** Specifies whether this device currently supports this tile. */
     val isAvailable: StateFlow<Boolean>
 
     /**
-     * Handles ViewModel lifecycle. Implementations should be inactive outside of
-     * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds.
-     */
-    fun onLifecycle(lifecycle: QSTileLifecycle)
-
-    /**
      * Notifies about the user change. Implementations should avoid using 3rd party userId sources
      * and use this value instead. This is to maintain consistent and concurrency-free behaviour
      * across different parts of QS.
      */
-    fun onUserIdChanged(userId: Int)
+    fun onUserChanged(user: UserHandle)
 
     /** Triggers the emission of the new [QSTileState] in a [state]. */
     fun forceUpdate()
 
     /** Notifies underlying logic about user input. */
     fun onActionPerformed(userAction: QSTileUserAction)
+
+    /**
+     * Frees the resources held by this view model. Call it when you no longer need the instance,
+     * because there is no guarantee it will work as expected beyond this point.
+     */
+    fun destroy()
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f6299e3..efa6da7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.content.Context
+import android.os.UserHandle
 import android.util.Log
 import android.view.View
 import androidx.annotation.GuardedBy
 import com.android.internal.logging.InstanceId
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
@@ -31,9 +33,7 @@
 import dagger.assisted.AssistedInject
 import java.util.function.Supplier
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.collectIndexed
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -44,6 +44,7 @@
 class QSTileViewModelAdapter
 @AssistedInject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val qsHost: QSHost,
     @Assisted private val qsTileViewModel: QSTileViewModel,
 ) : QSTile {
@@ -57,25 +58,28 @@
     private val listeningClients: MutableCollection<Any> = mutableSetOf()
 
     // Cancels the jobs when the adapter is no longer alive
-    private val adapterScope = CoroutineScope(SupervisorJob())
+    private var availabilityJob: Job? = null
     // Cancels the jobs when clients stop listening
-    private val listeningScope = CoroutineScope(SupervisorJob())
+    private var stateJob: Job? = null
 
     init {
-        adapterScope.launch {
-            qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
-                if (!isAvailable) {
-                    qsHost.removeTile(tileSpec)
-                }
-                // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's
-                // why we only allow isAvailable == true once and throw an exception afterwards.
-                if (index > 0 && isAvailable) {
-                    // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional
-                    // guidance on how to auto add your tile
-                    throw UnsupportedOperationException("Turning on tile is not supported now")
+        availabilityJob =
+            applicationScope.launch {
+                qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+                    if (!isAvailable) {
+                        qsHost.removeTile(tileSpec)
+                    }
+                    // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
+                    // That's
+                    // why we only allow isAvailable == true once and throw an exception afterwards.
+                    if (index > 0 && isAvailable) {
+                        // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
+                        // additional
+                        // guidance on how to auto add your tile
+                        throw UnsupportedOperationException("Turning on tile is not supported now")
+                    }
                 }
             }
-        }
 
         // QSTileHost doesn't call this when userId is initialized
         userSwitch(qsHost.userId)
@@ -131,7 +135,7 @@
         qsTileViewModel.currentState?.supportedActions?.contains(action) == true
 
     override fun userSwitch(currentUser: Int) {
-        qsTileViewModel.onUserIdChanged(currentUser)
+        qsTileViewModel.onUserChanged(UserHandle.of(currentUser))
     }
 
     @Deprecated(
@@ -140,25 +144,28 @@
     )
     override fun getMetricsCategory(): Int = 0
 
+    override fun isTileReady(): Boolean = qsTileViewModel.currentState != null
+
     override fun setListening(client: Any?, listening: Boolean) {
         client ?: return
         synchronized(listeningClients) {
             if (listening) {
                 listeningClients.add(client)
                 if (listeningClients.size == 1) {
-                    qsTileViewModel.state
-                        .map { mapState(context, it, qsTileViewModel.config) }
-                        .onEach { legacyState ->
-                            synchronized(callbacks) {
-                                callbacks.forEach { it.onStateChanged(legacyState) }
+                    stateJob =
+                        qsTileViewModel.state
+                            .map { mapState(context, it, qsTileViewModel.config) }
+                            .onEach { legacyState ->
+                                synchronized(callbacks) {
+                                    callbacks.forEach { it.onStateChanged(legacyState) }
+                                }
                             }
-                        }
-                        .launchIn(listeningScope)
+                            .launchIn(applicationScope)
                 }
             } else {
                 listeningClients.remove(client)
                 if (listeningClients.isEmpty()) {
-                    listeningScope.coroutineContext.cancelChildren()
+                    stateJob?.cancel()
                 }
             }
         }
@@ -172,9 +179,9 @@
     }
 
     override fun destroy() {
-        adapterScope.cancel()
-        listeningScope.cancel()
-        qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
+        stateJob?.cancel()
+        availabilityJob?.cancel()
+        qsTileViewModel.destroy()
     }
 
     override fun getState(): QSTile.State? =
@@ -182,7 +189,13 @@
 
     override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
     override fun getTileLabel(): CharSequence =
-        context.getString(qsTileViewModel.config.tileLabelRes)
+        with(qsTileViewModel.config.uiConfig) {
+            when (this) {
+                is QSTileUIConfig.Empty -> qsTileViewModel.currentState?.label ?: ""
+                is QSTileUIConfig.Resource -> context.getString(tileLabelRes)
+            }
+        }
+
     override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec
 
     private companion object {
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 f0650d3..9ba02b1 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
@@ -17,6 +17,8 @@
 package com.android.systemui.scene.shared.flag
 
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.FeatureFlags
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -47,15 +49,15 @@
 class SceneContainerFlagsImpl
 @AssistedInject
 constructor(
-    private val featureFlags: FeatureFlagsClassic,
+    private val featureFlagsClassic: FeatureFlagsClassic,
+    featureFlags: FeatureFlags,
     @Assisted private val isComposeAvailable: Boolean,
 ) : SceneContainerFlags {
 
     companion object {
         @VisibleForTesting
-        val flags: List<Flag<Boolean>> =
+        val classicFlagTokens: List<Flag<Boolean>> =
             listOf(
-                Flags.SCENE_CONTAINER,
                 Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA,
                 Flags.MIGRATE_LOCK_ICON,
                 Flags.MIGRATE_NSSL,
@@ -67,7 +69,13 @@
 
     /** The list of requirements, all must be met for the feature to be enabled. */
     private val requirements =
-        flags.map { FlagMustBeEnabled(it) } +
+        listOf(
+            AconfigFlagMustBeEnabled(
+                flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
+                flagValue = featureFlags.sceneContainer(),
+            ),
+        ) +
+            classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
             listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
 
     override fun isEnabled(): Boolean {
@@ -115,14 +123,25 @@
 
         override fun isMet(): Boolean {
             return when (flag) {
-                is ResourceBooleanFlag -> featureFlags.isEnabled(flag)
-                is ReleasedFlag -> featureFlags.isEnabled(flag)
-                is UnreleasedFlag -> featureFlags.isEnabled(flag)
+                is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
+                is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
+                is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
                 else -> error("Unsupported flag type ${flag.javaClass}")
             }
         }
     }
 
+    private inner class AconfigFlagMustBeEnabled(
+        flagName: String,
+        private val flagValue: Boolean,
+    ) : Requirement {
+        override val name: String = "Aconfig flag $flagName must be enabled"
+
+        override fun isMet(): Boolean {
+            return flagValue
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
index f704894..3927873 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
@@ -42,13 +42,6 @@
          * scene, this value will remain true after the pointer is no longer touching the screen and
          * will be true in any transition created to animate back to the original position.
          */
-        val isInitiatedByUserInput: Boolean,
-
-        /**
-         * Whether user input is currently driving the transition. For example, if a user is
-         * dragging a pointer, this emits true. Once they lift their finger, this emits false while
-         * the transition completes/settles.
-         */
-        val isUserInputOngoing: Flow<Boolean>,
+        val isUserInputDriven: Boolean,
     ) : ObservableTransitionState()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 7a0c087..05f125f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -25,6 +25,7 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.os.CountDownTimer;
+import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 
@@ -38,6 +39,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.plugins.ActivityStarter;
@@ -45,13 +48,13 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.CallbackController;
 
+import dagger.Lazy;
+
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Helper class to initiate a screen recording
  */
@@ -71,6 +74,7 @@
     private final FeatureFlags mFlags;
     private final UserContextProvider mUserContextProvider;
     private final UserTracker mUserTracker;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     protected static final String INTENT_UPDATE_STATE =
             "com.android.systemui.screenrecord.UPDATE_STATE";
@@ -115,7 +119,8 @@
             FeatureFlags flags,
             UserContextProvider userContextProvider,
             Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
         mMainExecutor = mainExecutor;
         mContext = context;
         mFlags = flags;
@@ -123,6 +128,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mUserContextProvider = userContextProvider;
         mUserTracker = userTracker;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
 
         BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setInteractive(true);
@@ -136,6 +142,13 @@
         return UserHandle.of(UserHandle.myUserId());
     }
 
+    /**
+     * MediaProjection host is SystemUI for the screen recorder, so return 'my process uid'
+     */
+    private int getHostUid() {
+        return Process.myUid();
+    }
+
     /** Create a dialog to show screen recording options to the user.
      *  If screen capturing is currently not allowed it will return a dialog
      *  that warns users about it. */
@@ -149,10 +162,23 @@
             return new ScreenCaptureDisabledDialog(mContext);
         }
 
+        mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+
         return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
-                ? new ScreenRecordPermissionDialog(context,  getHostUserHandle(), this,
-                    activityStarter, mUserContextProvider, onStartRecordingClicked)
-                : new ScreenRecordDialog(context, this, mUserContextProvider,
+                ? new ScreenRecordPermissionDialog(
+                        context,
+                        getHostUserHandle(),
+                        getHostUid(),
+                        /* controller= */ this,
+                        activityStarter,
+                        mUserContextProvider,
+                        onStartRecordingClicked,
+                        mMediaProjectionMetricsLogger)
+                : new ScreenRecordDialog(
+                        context,
+                        /* controller= */ this,
+                        mUserContextProvider,
                         onStartRecordingClicked);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index f2e94e9..e7481cc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -66,8 +66,10 @@
     private Switch mAudioSwitch;
     private Spinner mOptions;
 
-    public ScreenRecordDialog(Context context, RecordingController controller,
-            UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) {
+    public ScreenRecordDialog(Context context,
+                              RecordingController controller,
+                              UserContextProvider userContextProvider,
+                              @Nullable Runnable onStartRecordingClicked) {
         super(context);
         mController = controller;
         mUserContextProvider = userContextProvider;
@@ -89,7 +91,6 @@
 
         TextView cancelBtn = findViewById(R.id.button_cancel);
         cancelBtn.setOnClickListener(v -> dismiss());
-
         TextView startBtn = findViewById(R.id.button_start);
         startBtn.setOnClickListener(v -> {
             if (mOnStartRecordingClicked != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index a1d5d98..f74234b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -33,6 +33,7 @@
 import android.widget.Switch
 import androidx.annotation.LayoutRes
 import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
 import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog
 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -46,15 +47,19 @@
 class ScreenRecordPermissionDialog(
     context: Context,
     private val hostUserHandle: UserHandle,
+    private val hostUid: Int,
     private val controller: RecordingController,
     private val activityStarter: ActivityStarter,
     private val userContextProvider: UserContextProvider,
-    private val onStartRecordingClicked: Runnable?
+    private val onStartRecordingClicked: Runnable?,
+    mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
     BaseScreenSharePermissionDialog(
         context,
         createOptionList(),
         appName = null,
+        hostUid = hostUid,
+        mediaProjectionMetricsLogger,
         R.drawable.ic_screenrecord,
         R.color.screenrecord_icon_color
     ) {
@@ -88,6 +93,7 @@
                     MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
                     hostUserHandle
                 )
+                intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
                 activityStarter.startActivity(intent, /* dismissShade= */ true)
             }
             dismiss()
@@ -186,14 +192,14 @@
         private fun createOptionList(): List<ScreenShareOption> {
             return listOf(
                 ScreenShareOption(
-                    ENTIRE_SCREEN,
-                    R.string.screen_share_permission_dialog_option_entire_screen,
-                    R.string.screenrecord_permission_dialog_warning_entire_screen
-                ),
-                ScreenShareOption(
                     SINGLE_APP,
                     R.string.screen_share_permission_dialog_option_single_app,
                     R.string.screenrecord_permission_dialog_warning_single_app
+                ),
+                ScreenShareOption(
+                    ENTIRE_SCREEN,
+                    R.string.screen_share_permission_dialog_option_entire_screen,
+                    R.string.screenrecord_permission_dialog_warning_entire_screen
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index aa6bfc3..10d5f59 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -34,11 +34,11 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.util.TraceUtils.Companion.launch
 import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 @SysUISingleton
@@ -63,7 +63,9 @@
         user: UserHandle,
         overrideTransition: Boolean,
     ) {
-        applicationScope.launch { launchIntent(intent, options, user, overrideTransition) }
+        applicationScope.launch("$TAG#launchIntentAsync") {
+            launchIntent(intent, options, user, overrideTransition)
+        }
     }
 
     suspend fun launchIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 9325e18..00d480a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.ContentProvider;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.HardwareRenderer;
@@ -44,10 +45,10 @@
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.view.OneShotPreDrawListener;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.screenshot.CropView.CropBoundary;
 import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
 import com.android.systemui.settings.UserTracker;
@@ -421,13 +422,15 @@
             Log.e(TAG, "failed to export", e);
             return;
         }
+        Uri exported = ContentProvider.getUriWithoutUserId(result.uri);
+        Log.e(TAG, action + " uri=" + exported);
 
         switch (action) {
             case EDIT:
-                doEdit(result.uri);
+                doEdit(exported);
                 break;
             case SHARE:
-                doShare(result.uri);
+                doShare(exported);
                 break;
             case SAVE:
                 // Nothing more to do
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 5154067..f1c74c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,9 +20,8 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.util.TraceUtils.Companion.launch
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 import java.util.function.Consumer
 import javax.inject.Inject
 
@@ -36,16 +35,15 @@
     suspend fun process(screenshot: ScreenshotData): ScreenshotData
 }
 
-/**
- * Implementation of [ScreenshotRequestProcessor]
- */
+/** Implementation of [ScreenshotRequestProcessor] */
 @SysUISingleton
-class RequestProcessor @Inject constructor(
-        private val capture: ImageCapture,
-        private val policy: ScreenshotPolicy,
-        private val flags: FeatureFlags,
-        /** For the Java Async version, to invoke the callback. */
-        @Application private val mainScope: CoroutineScope
+class RequestProcessor
+@Inject
+constructor(
+    private val capture: ImageCapture,
+    private val policy: ScreenshotPolicy,
+    /** For the Java Async version, to invoke the callback. */
+    @Application private val mainScope: CoroutineScope
 ) : ScreenshotRequestProcessor {
 
     override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -67,8 +65,9 @@
             result.userHandle = info.user
 
             if (policy.isManagedProfile(info.user.identifier)) {
-                val image = capture.captureTask(info.taskId)
-                    ?: error("Task snapshot returned a null Bitmap!")
+                val image =
+                    capture.captureTask(info.taskId)
+                        ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
 
                 // Provide the task snapshot as the screenshot
                 result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -89,7 +88,7 @@
      *                 thread
      */
     fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
-        mainScope.launch {
+        mainScope.launch({ "$TAG#processAsync" }) {
             val result = process(screenshot)
             callback.accept(result)
         }
@@ -97,3 +96,6 @@
 }
 
 private const val TAG = "RequestProcessor"
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4b3bd0b..21a08a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -303,6 +303,13 @@
     private String mPackageName = "";
     private BroadcastReceiver mCopyBroadcastReceiver;
 
+    // When false, the screenshot is taken without showing the ui. Note that this only applies to
+    // external displays, as on the default one the UI should **always** be shown.
+    // This is needed in case of screenshot during display mirroring, as adding another window to
+    // the external display makes mirroring stop.
+    // When there is a way to distinguish between displays that are mirroring or extending, this
+    // can be removed and we can directly show the ui only in the extended case.
+    private final Boolean mShowUIOnExternalDisplay;
     /** Tracks config changes that require re-creating UI */
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_ORIENTATION
@@ -318,7 +325,7 @@
             Context context,
             FeatureFlags flags,
             ScreenshotSmartActions screenshotSmartActions,
-            ScreenshotNotificationsController screenshotNotificationsController,
+            ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
             ScrollCaptureClient scrollCaptureClient,
             UiEventLogger uiEventLogger,
             ImageExporter imageExporter,
@@ -335,10 +342,11 @@
             AssistContentRequester assistContentRequester,
             MessageContainerController messageContainerController,
             Provider<ScreenshotSoundController> screenshotSoundController,
-            @Assisted int displayId
+            @Assisted int displayId,
+            @Assisted boolean showUIOnExternalDisplay
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
-        mNotificationsController = screenshotNotificationsController;
+        mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
         mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
@@ -401,6 +409,7 @@
         mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
                         ClipboardOverlayController.COPY_OVERLAY_ACTION),
                 ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+        mShowUIOnExternalDisplay = showUIOnExternalDisplay;
     }
 
     void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
@@ -448,6 +457,23 @@
 
         prepareViewForNewScreenshot(screenshot, oldPackageName);
 
+        if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
+            mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+                    new AssistContentRequester.Callback() {
+                        @Override
+                        public void onAssistContentAvailable(AssistContent assistContent) {
+                            screenshot.setContextUrl(assistContent.getWebUri());
+                        }
+                    });
+        }
+
+        if (!shouldShowUi()) {
+            saveScreenshotInWorkerThread(
+                screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
+                (ignored) -> {});
+            return;
+        }
+
         saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
                 this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
 
@@ -482,22 +508,16 @@
                 screenshot.getUserHandle()));
         mScreenshotView.setScreenshot(screenshot);
 
-        if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
-            mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
-                    new AssistContentRequester.Callback() {
-                        @Override
-                        public void onAssistContentAvailable(AssistContent assistContent) {
-                            screenshot.setContextUrl(assistContent.getWebUri());
-                        }
-                    });
-        }
-
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> WindowInsets.CONSUMED);
         mScreenshotHandler.cancelTimeout(); // restarted after animation
     }
 
+    private boolean shouldShowUi() {
+        return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
+    }
+
     void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
         withWindowAttached(() -> {
             if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
@@ -1199,7 +1219,13 @@
     /** Injectable factory to create screenshot controller instances for a specific display. */
     @AssistedFactory
     public interface Factory {
-        /** Creates an instance of the controller for that specific displayId. */
-        ScreenshotController create(int displayId);
+        /**
+         * Creates an instance of the controller for that specific displayId.
+         *
+         * @param displayId:               display to capture
+         * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external
+         *                                 display.
+         */
+        ScreenshotController create(int displayId, boolean showUIOnExternalDisplay);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
deleted file mode 100644
index 4344fd1..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.Context.NOTIFICATION_SERVICE;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.res.R;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.util.NotificationChannels;
-
-import javax.inject.Inject;
-
-/**
- * Convenience class to handle showing and hiding notifications while taking a screenshot.
- */
-public class ScreenshotNotificationsController {
-    private static final String TAG = "ScreenshotNotificationManager";
-
-    private final Context mContext;
-    private final Resources mResources;
-    private final NotificationManager mNotificationManager;
-
-    @Inject
-    ScreenshotNotificationsController(Context context, WindowManager windowManager) {
-        mContext = context;
-        mResources = context.getResources();
-        mNotificationManager =
-                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
-
-        DisplayMetrics displayMetrics = new DisplayMetrics();
-        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
-    }
-
-    /**
-     * Sends a notification that the screenshot capture has failed.
-     */
-    public void notifyScreenshotError(int msgResId) {
-        Resources res = mContext.getResources();
-        String errorMsg = res.getString(msgResId);
-
-        // Repurpose the existing notification to notify the user of the error
-        Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
-                .setTicker(res.getString(R.string.screenshot_failed_title))
-                .setContentTitle(res.getString(R.string.screenshot_failed_title))
-                .setContentText(errorMsg)
-                .setSmallIcon(R.drawable.stat_notify_image_error)
-                .setWhen(System.currentTimeMillis())
-                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
-                .setCategory(Notification.CATEGORY_ERROR)
-                .setAutoCancel(true)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
-        final DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        final Intent intent =
-                dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
-        if (intent != null) {
-            final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
-            b.setContentIntent(pendingIntent);
-        }
-
-        SystemUIApplication.overrideNotificationAppName(mContext, b, true);
-
-        Notification n = new Notification.BigTextStyle(b)
-                .bigText(errorMsg)
-                .build();
-        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
new file mode 100644
index 0000000..d874eb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserHandle
+import android.view.Display
+import com.android.internal.R
+import com.android.internal.messages.nano.SystemMessageProto
+import com.android.systemui.SystemUIApplication
+import com.android.systemui.util.NotificationChannels
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Convenience class to handle showing and hiding notifications while taking a screenshot. */
+class ScreenshotNotificationsController
+@AssistedInject
+internal constructor(
+    @Assisted private val displayId: Int,
+    private val context: Context,
+    private val notificationManager: NotificationManager,
+    private val devicePolicyManager: DevicePolicyManager,
+) {
+    private val res = context.resources
+
+    /**
+     * Sends a notification that the screenshot capture has failed.
+     *
+     * Errors for the non-default display are shown in a unique separate notification.
+     */
+    fun notifyScreenshotError(msgResId: Int) {
+        val displayErrorString =
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                " ($externalDisplayString)"
+            } else {
+                ""
+            }
+        val errorMsg = res.getString(msgResId) + displayErrorString
+
+        // Repurpose the existing notification or create a new one
+        val builder =
+            Notification.Builder(context, NotificationChannels.ALERTS)
+                .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title))
+                .setContentTitle(
+                    res.getString(com.android.systemui.res.R.string.screenshot_failed_title)
+                )
+                .setContentText(errorMsg)
+                .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error)
+                .setWhen(System.currentTimeMillis())
+                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+                .setCategory(Notification.CATEGORY_ERROR)
+                .setAutoCancel(true)
+                .setColor(context.getColor(R.color.system_notification_accent_color))
+        val intent =
+            devicePolicyManager.createAdminSupportIntent(
+                DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE
+            )
+        if (intent != null) {
+            val pendingIntent =
+                PendingIntent.getActivityAsUser(
+                    context,
+                    0,
+                    intent,
+                    PendingIntent.FLAG_IMMUTABLE,
+                    null,
+                    UserHandle.CURRENT
+                )
+            builder.setContentIntent(pendingIntent)
+        }
+        SystemUIApplication.overrideNotificationAppName(context, builder, true)
+        val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build()
+        // A different id for external displays to keep the 2 error notifications separated.
+        val id =
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT
+            } else {
+                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY
+            }
+        notificationManager.notify(id, notification)
+    }
+
+    private val externalDisplayString: String
+        get() =
+            res.getString(
+                com.android.systemui.res.R.string.screenshot_failed_external_display_indication
+            )
+
+    /** Factory for [ScreenshotNotificationsController]. */
+    @AssistedFactory
+    interface Factory {
+        fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 14e875d2..d2e4794 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.shade.ShadeExpansionStateManager
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.launch
+import com.android.systemui.util.TraceUtils.Companion.launch
 import kotlinx.coroutines.withContext
 
 /** Provides state from the main SystemUI process on behalf of the Screenshot process. */
@@ -47,7 +47,9 @@
             }
 
             override fun dismissKeyguard(callback: IOnDoneCallback) {
-                lifecycleScope.launch { executeAfterDismissing(callback) }
+                lifecycleScope.launch("IScreenshotProxy#dismissKeyguard") {
+                    executeAfterDismissing(callback)
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index 070fb1e..049799e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,25 +16,29 @@
 
 package com.android.systemui.screenshot;
 
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.view.WindowManager;
+import android.view.Display;
 
 import com.android.systemui.res.R;
 
 /**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
+ * Receives errors related to screenshot.
  */
 public class ScreenshotServiceErrorReceiver extends BroadcastReceiver {
 
     @Override
     public void onReceive(final Context context, Intent intent) {
         // Show a message that we've failed to save the image to disk
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        ScreenshotNotificationsController controller =
-                new ScreenshotNotificationsController(context, wm);
+        NotificationManager notificationManager = context.getSystemService(
+                NotificationManager.class);
+        DevicePolicyManager devicePolicyManager = context.getSystemService(
+                DevicePolicyManager.class);
+        ScreenshotNotificationsController controller = new ScreenshotNotificationsController(
+                Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager);
         controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index cd0cab5..1eae191 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -20,7 +20,7 @@
 import android.util.Log
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.util.TraceUtils.Companion.tracedAsync
+import com.android.systemui.util.TraceUtils.Companion.async
 import com.google.errorprone.annotations.CanIgnoreReturnValue
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
@@ -48,7 +48,7 @@
 ) : ScreenshotSoundController {
 
     val player: Deferred<MediaPlayer?> =
-        coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) {
+        coroutineScope.async("loadCameraSound", bgDispatcher) {
             try {
                 soundProvider.getScreenshotSound()
             } catch (e: IllegalStateException) {
@@ -58,12 +58,10 @@
         }
 
     override fun playCameraSound(): Deferred<Unit> {
-        return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) {
-            player.await()?.start()
-        }
+        return coroutineScope.async("playCameraSound", bgDispatcher) { player.await()?.start() }
     }
     override fun releaseScreenshotSound(): Deferred<Unit> {
-        return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) {
+        return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
             try {
                 withTimeout(1.seconds) { player.await()?.release() }
             } catch (e: TimeoutCancellationException) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 03c3f7a..5684605 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -10,15 +10,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.TraceUtils.Companion.launch
 import java.util.function.Consumer
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.first
 
 /**
  * Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
@@ -34,16 +33,13 @@
     displayRepository: DisplayRepository,
     @Application private val mainScope: CoroutineScope,
     private val screenshotRequestProcessor: ScreenshotRequestProcessor,
-    private val uiEventLogger: UiEventLogger
+    private val uiEventLogger: UiEventLogger,
+    private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
 ) {
 
-    private lateinit var displays: StateFlow<Set<Display>>
-    private val displaysCollectionJob: Job =
-        mainScope.launch {
-            displays = displayRepository.displays.stateIn(this, SharingStarted.Eagerly, emptySet())
-        }
-
+    private val displays = displayRepository.displays
     private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+    private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
 
     /**
      * Executes the [ScreenshotRequest].
@@ -58,48 +54,77 @@
     ) {
         val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
-        screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
+        displayIds.forEach { displayId: Int ->
+            Log.d(TAG, "Executing screenshot for display $displayId")
             dispatchToController(
-                screenshotData = screenshotData,
+                rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
                 onSaved =
-                    if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> },
-                callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId)
+                    if (displayId == Display.DEFAULT_DISPLAY) {
+                        onSaved
+                    } else { _ -> },
+                callback = resultCallbackWrapper.createCallbackForId(displayId)
             )
         }
     }
 
-    /** Creates a [ScreenshotData] for each display. */
-    private suspend fun ScreenshotRequest.oneForEachDisplay(
-        displayIds: List<Int>
-    ): List<ScreenshotData> {
-        return displayIds
-            .map { displayId -> ScreenshotData.fromRequest(this, displayId) }
-            .map { screenshotData: ScreenshotData ->
-                screenshotRequestProcessor.process(screenshotData)
-            }
-    }
-
-    private fun dispatchToController(
-        screenshotData: ScreenshotData,
+    /** All logging should be triggered only by this method. */
+    private suspend fun dispatchToController(
+        rawScreenshotData: ScreenshotData,
         onSaved: (Uri) -> Unit,
         callback: RequestCallback
     ) {
+        // Let's wait before logging "screenshot requested", as we should log the processed
+        // ScreenshotData.
+        val screenshotData =
+            try {
+                screenshotRequestProcessor.process(rawScreenshotData)
+            } catch (e: RequestProcessorException) {
+                Log.e(TAG, "Failed to process screenshot request!", e)
+                logScreenshotRequested(rawScreenshotData)
+                onFailedScreenshotRequest(rawScreenshotData, callback)
+                return
+            }
+
+        logScreenshotRequested(screenshotData)
+        Log.d(TAG, "Screenshot request: $screenshotData")
+        try {
+            getScreenshotController(screenshotData.displayId)
+                .handleScreenshot(screenshotData, onSaved, callback)
+        } catch (e: IllegalStateException) {
+            Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
+            onFailedScreenshotRequest(screenshotData, callback)
+            return // After a failure log, nothing else should run.
+        }
+    }
+
+    /**
+     * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED]
+     * event.
+     */
+    private fun logScreenshotRequested(screenshotData: ScreenshotData) {
         uiEventLogger.log(
             ScreenshotEvent.getScreenshotSource(screenshotData.source),
             0,
             screenshotData.packageNameString
         )
-        Log.d(TAG, "Screenshot request: $screenshotData")
-        getScreenshotController(screenshotData.displayId)
-            .handleScreenshot(screenshotData, onSaved, callback)
     }
 
-    private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
+    private fun onFailedScreenshotRequest(
+        screenshotData: ScreenshotData,
+        callback: RequestCallback
+    ) {
+        uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString)
+        getNotificationController(screenshotData.displayId)
+            .notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
+        callback.reportError()
+    }
+
+    private suspend fun getDisplaysToScreenshot(requestType: Int): List<Int> {
         return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
             // If this is a provided image, let's show the UI on the default display only.
             listOf(Display.DEFAULT_DISPLAY)
         } else {
-            displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+            displays.first().filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
         }
     }
 
@@ -131,11 +156,18 @@
             screenshotController.onDestroy()
         }
         screenshotControllers.clear()
-        displaysCollectionJob.cancel()
     }
 
     private fun getScreenshotController(id: Int): ScreenshotController {
-        return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) }
+        return screenshotControllers.computeIfAbsent(id) {
+            screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false)
+        }
+    }
+
+    private fun getNotificationController(id: Int): ScreenshotNotificationsController {
+        return notificationControllers.computeIfAbsent(id) {
+            screenshotNotificationControllerFactory.create(id)
+        }
     }
 
     /** For java compatibility only. see [executeScreenshots] */
@@ -144,7 +176,7 @@
         onSaved: Consumer<Uri>,
         requestCallback: RequestCallback
     ) {
-        mainScope.launch {
+        mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") {
             executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0be2265..0991c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -113,7 +113,8 @@
     @Inject
     public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
             UserManager userManager, DevicePolicyManager devicePolicyManager,
-            UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController,
+            UiEventLogger uiEventLogger,
+            ScreenshotNotificationsController.Factory notificationsControllerFactory,
             Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
             RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
         if (DEBUG_SERVICE) {
@@ -123,7 +124,7 @@
         mUserManager = userManager;
         mDevicePolicyManager = devicePolicyManager;
         mUiEventLogger = uiEventLogger;
-        mNotificationsController = notificationsController;
+        mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
         mContext = context;
         mBgExecutor = bgExecutor;
         mFeatureFlags = featureFlags;
@@ -132,7 +133,8 @@
         if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
             mScreenshot = null;
         } else {
-            mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY);
+            mScreenshot = screenshotControllerFactory.create(
+                    Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
         }
     }
 
@@ -245,15 +247,17 @@
         Log.d(TAG, "Processing screenshot data");
 
 
-        ScreenshotData screenshotData = ScreenshotData.fromRequest(
-                request, Display.DEFAULT_DISPLAY);
+        if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+            mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
+            return;
+        }
+        // TODO(b/295143676): Delete the following after the flag is released.
         try {
-            if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
-                mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
-            } else {
-                mProcessor.processAsync(screenshotData, (data) ->
-                        dispatchToController(data, onSaved, callback));
-            }
+            ScreenshotData screenshotData = ScreenshotData.fromRequest(
+                    request, Display.DEFAULT_DISPLAY);
+            mProcessor.processAsync(screenshotData, (data) ->
+                    dispatchToController(data, onSaved, callback));
+
         } catch (IllegalStateException e) {
             Log.e(TAG, "Failed to process screenshot request!", e);
             logFailedRequest(request);
@@ -263,6 +267,7 @@
         }
     }
 
+    // TODO(b/295143676): Delete this.
     private void dispatchToController(ScreenshotData screenshot,
             Consumer<Uri> uriConsumer, RequestCallback callback) {
         mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index bd592c9..d6f1ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.settings
 
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
+
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -64,7 +66,12 @@
     /**
      * Callback for notifying of changes.
      */
+    @WeaklyReferencedCallback
     interface Callback {
+        /**
+         * Notifies that the current user will be changed.
+         */
+        fun onBeforeUserSwitching(newUser: Int) {}
 
         /**
          * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 393a698..9f416bb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -90,6 +90,7 @@
     private val isBackgroundUserSwitchEnabled: Boolean get() =
         featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS)
 
+    @Deprecated("Use UserInteractor.getSelectedUserId()")
     override var userId: Int by SynchronizedDelegate(context.userId)
         protected set
 
@@ -226,6 +227,13 @@
     protected open fun handleBeforeUserSwitching(newUserId: Int) {
         Assert.isNotMainThread()
         setUserIdInternal(newUserId)
+
+        val list = synchronized(callbacks) {
+            callbacks.toList()
+        }
+        list.forEach {
+            it.callback.get()?.onBeforeUserSwitching(newUserId)
+        }
     }
 
     @WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6783afa..ead10d6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,11 +16,13 @@
 
 package com.android.systemui.settings.brightness;
 
+import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
 
 import android.app.Activity;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Gravity;
@@ -35,8 +37,8 @@
 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.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -74,21 +76,26 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setWindowAttributes();
+        setContentView(R.layout.brightness_mirror_container);
+        setBrightnessDialogViewAttributes();
+    }
 
+    private void setWindowAttributes() {
         final Window window = getWindow();
 
-        window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+        window.setGravity(Gravity.TOP | Gravity.START);
         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
         window.requestFeature(Window.FEATURE_NO_TITLE);
 
         // Calling this creates the decor View, so setLayout takes proper effect
         // (see Dialog#onWindowAttributesChanged)
         window.getDecorView();
-        window.setLayout(
-                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
         getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
+    }
 
-        setContentView(R.layout.brightness_mirror_container);
+    void setBrightnessDialogViewAttributes() {
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
         // The brightness mirror container is INVISIBLE by default.
         frame.setVisibility(View.VISIBLE);
@@ -97,6 +104,14 @@
                 getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
         lp.leftMargin = horizontalMargin;
         lp.rightMargin = horizontalMargin;
+
+        int verticalMargin =
+                getResources().getDimensionPixelSize(
+                        R.dimen.notification_guts_option_vertical_padding);
+
+        lp.topMargin = verticalMargin;
+        lp.bottomMargin = verticalMargin;
+
         frame.setLayoutParams(lp);
         Rect bounds = new Rect();
         frame.addOnLayoutChangeListener(
@@ -113,6 +128,21 @@
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
         mBrightnessController = mBrightnessControllerFactory.create(controller);
+
+        Configuration configuration = getResources().getConfiguration();
+        int orientation = configuration.orientation;
+        int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
+
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            boolean shouldBeFullWidth = getIntent()
+                    .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
+            lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
+        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+            lp.width = screenWidth - horizontalMargin * 2;
+        }
+
+        frame.setLayoutParams(lp);
+
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ba0cf08..cc59f87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -414,9 +414,7 @@
     private int mDisplayLeftInset = 0; // in pixels
 
     @VisibleForTesting
-    KeyguardClockPositionAlgorithm
-            mClockPositionAlgorithm =
-            new KeyguardClockPositionAlgorithm();
+    KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private final KeyguardClockPositionAlgorithm.Result
             mClockPositionResult =
             new KeyguardClockPositionAlgorithm.Result();
@@ -779,7 +777,8 @@
             KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
-            PowerInteractor powerInteractor) {
+            PowerInteractor powerInteractor,
+            KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -807,6 +806,7 @@
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
+        mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -1446,9 +1446,7 @@
                     mBarState);
         }
 
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            setKeyguardVisibility(mBarState, false);
-        } else {
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             setKeyguardBottomAreaVisibility(mBarState, false);
         }
 
@@ -2358,14 +2356,6 @@
         }
     }
 
-    private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
-        mKeyguardInteractor.setKeyguardRootVisibility(
-            statusBarState,
-            goingToFullShade,
-            mIsOcclusionTransitionRunning
-        );
-    }
-
     @Deprecated
     private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
         mKeyguardBottomArea.animate().cancel();
@@ -2639,7 +2629,6 @@
         if (isPanelExpanded() != isExpanded) {
             setExpandedOrAwaitingInputTransfer(isExpanded);
             updateSystemUiStateFlags();
-            mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
             if (!isExpanded) {
                 mQsController.closeQsCustomizer();
             }
@@ -4443,11 +4432,13 @@
                     && statusBarState == KEYGUARD) {
                 // This means we're doing the screen off animation - position the keyguard status
                 // view where it'll be on AOD, so we can animate it in.
-                mKeyguardStatusViewController.updatePosition(
-                        mClockPositionResult.clockX,
-                        mClockPositionResult.clockYFullyDozing,
-                        mClockPositionResult.clockScale,
-                        false /* animate */);
+                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                    mKeyguardStatusViewController.updatePosition(
+                            mClockPositionResult.clockX,
+                            mClockPositionResult.clockYFullyDozing,
+                            mClockPositionResult.clockScale,
+                            false /* animate */);
+                }
             }
 
             mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
@@ -4456,9 +4447,7 @@
                     goingToFullShade,
                     mBarState);
 
-            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-                setKeyguardVisibility(statusBarState, goingToFullShade);
-            } else {
+            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
                 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
             }
 
@@ -4562,7 +4551,12 @@
     public void showAodUi() {
         setDozing(true /* dozing */, false /* animate */);
         mStatusBarStateController.setUpcomingState(KEYGUARD);
-        mStatusBarStateListener.onStateChanged(KEYGUARD);
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            mStatusBarStateController.setState(KEYGUARD);
+        } else {
+            mStatusBarStateListener.onStateChanged(KEYGUARD);
+        }
         mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
         setExpandedFraction(1f);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 6f5e41f..0426388 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -47,7 +47,6 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -72,6 +71,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -112,6 +112,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private final Executor mBackgroundExecutor;
     private final AuthController mAuthController;
+    private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
@@ -157,7 +158,8 @@
             AuthController authController,
             ShadeExpansionStateManager shadeExpansionStateManager,
             Lazy<ShadeInteractor> shadeInteractorLazy,
-            ShadeWindowLogger logger) {
+            ShadeWindowLogger logger,
+            Lazy<SelectedUserInteractor> userInteractor) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
         mWindowManager = windowManager;
@@ -174,6 +176,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         dumpManager.registerDumpable(this);
         mAuthController = authController;
+        mUserInteractor = userInteractor;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -348,7 +351,7 @@
             boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
                     && !state.keyguardFadingAway && !state.keyguardGoingAway;
             if (onKeyguard
-                    && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+                    && mAuthController.isUdfpsEnrolled(mUserInteractor.get().getSelectedUserId())) {
                 // both max and min display refresh rate must be set to take effect:
                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate;
                 mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5414b3f..a2627ed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -36,26 +36,27 @@
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.back.domain.interactor.BackActionInteractor;
 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.classifier.FalsingCollector;
+import com.android.systemui.communal.data.repository.CommunalRepository;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.log.BouncerLogger;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -64,7 +65,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -75,6 +76,7 @@
 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.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -104,9 +106,11 @@
     private final PulsingGestureListener mPulsingGestureListener;
     private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
+    private final CommunalViewModel mCommunalViewModel;
+    private final CommunalRepository mCommunalRepository;
     private final boolean mIsTrackpadCommonEnabled;
-    private final FeatureFlags mFeatureFlags;
-    private final KeyEventInteractor mKeyEventInteractor;
+    private final FeatureFlagsClassic mFeatureFlagsClassic;
+    private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private GestureDetector mPulsingWakeupGestureHandler;
@@ -128,8 +132,6 @@
     private final CentralSurfaces mService;
     private final DozeServiceHost mDozeServiceHost;
     private final DozeScrimController mDozeScrimController;
-    private final BackActionInteractor mBackActionInteractor;
-    private final PowerInteractor mPowerInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mExpandingBelowNotch;
@@ -164,8 +166,6 @@
             CentralSurfaces centralSurfaces,
             DozeServiceHost dozeServiceHost,
             DozeScrimController dozeScrimController,
-            BackActionInteractor backActionInteractor,
-            PowerInteractor powerInteractor,
             NotificationShadeWindowController controller,
             Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -180,14 +180,17 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
-            NotificationExpansionRepository notificationExpansionRepository,
-            FeatureFlags featureFlags,
+            CommunalViewModel communalViewModel,
+            CommunalRepository communalRepository,
+            NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
+            FeatureFlagsClassic featureFlagsClassic,
             SystemClock clock,
             BouncerMessageInteractor bouncerMessageInteractor,
             BouncerLogger bouncerLogger,
-            KeyEventInteractor keyEventInteractor,
+            SysUIKeyEventHandler sysUIKeyEventHandler,
             PrimaryBouncerInteractor primaryBouncerInteractor,
-            AlternateBouncerInteractor alternateBouncerInteractor) {
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            SelectedUserInteractor selectedUserInteractor) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -200,21 +203,21 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
-        mBackActionInteractor = backActionInteractor;
         mShadeLogger = shadeLogger;
         mService = centralSurfaces;
         mDozeServiceHost = dozeServiceHost;
         mDozeScrimController = dozeScrimController;
-        mPowerInteractor = powerInteractor;
         mNotificationShadeWindowController = controller;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
         mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
-        mFeatureFlags = featureFlags;
-        mKeyEventInteractor = keyEventInteractor;
+        mCommunalViewModel = communalViewModel;
+        mCommunalRepository = communalRepository;
+        mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
+        mFeatureFlagsClassic = featureFlagsClassic;
+        mSysUIKeyEventHandler = sysUIKeyEventHandler;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
 
@@ -229,17 +232,18 @@
                 messageAreaControllerFactory,
                 bouncerMessageInteractor,
                 bouncerLogger,
-                featureFlags);
+                featureFlagsClassic,
+                selectedUserInteractor);
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
         collectFlow(
                 mView,
-                notificationExpansionRepository.isExpandAnimationRunning(),
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
                 this::setExpandAnimationRunning);
 
         mClock = clock;
-        if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
+        if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
             unfoldTransitionProgressProvider.ifPresent(
                     progressProvider -> progressProvider.addCallback(
                             mDisableSubpixelTextTransitionListener));
@@ -268,7 +272,7 @@
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
-        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+        if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
             mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                     mLockscreenHostedDreamGestureListener);
         }
@@ -440,7 +444,7 @@
                 }
 
                 boolean bouncerShowing;
-                if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
                     bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
                             || mAlternateBouncerInteractor.isVisibleState();
                 } else {
@@ -452,7 +456,7 @@
                     if (mDragDownHelper.isDragDownEnabled()) {
                         // This handles drag down over lockscreen
                         boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
-                        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                             if (result) {
                                 mLastInterceptWasDragDownHelper = true;
                                 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -484,7 +488,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                     mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
                 }
                 cancellation.recycle();
@@ -499,7 +503,7 @@
                 if (mStatusBarKeyguardViewManager.onTouch(ev)) {
                     return true;
                 }
-                if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                     if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
                         // we still want to finish our drag down gesture when locking the screen
                         handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -529,17 +533,17 @@
 
             @Override
             public boolean interceptMediaKey(KeyEvent event) {
-                return mKeyEventInteractor.interceptMediaKey(event);
+                return mSysUIKeyEventHandler.interceptMediaKey(event);
             }
 
             @Override
             public boolean dispatchKeyEventPreIme(KeyEvent event) {
-                return mKeyEventInteractor.dispatchKeyEventPreIme(event);
+                return mSysUIKeyEventHandler.dispatchKeyEventPreIme(event);
             }
 
             @Override
             public boolean dispatchKeyEvent(KeyEvent event) {
-                return mKeyEventInteractor.dispatchKeyEvent(event);
+                return mSysUIKeyEventHandler.dispatchKeyEvent(event);
             }
         });
 
@@ -564,8 +568,28 @@
         mDepthController.onPanelExpansionChanged(currentState);
     }
 
+    /**
+     * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
+     * is enabled.
+     *
+     * The layout lives in {@link R.id.communal_ui_container}.
+     */
+    public void setupCommunalHubLayout() {
+        if (!mCommunalRepository.isCommunalEnabled()
+                || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+            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(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
+                mCommunalViewModel), index);
+    }
+
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
             // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
             // to also ask NotificationPanelViewController directly, in order to process swipe up
             // events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 3bbb2cf..0ec7a36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -65,7 +65,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
@@ -77,6 +76,7 @@
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -736,7 +736,11 @@
 
     /** Returns whether touches from the notification panel should be disallowed */
     public boolean disallowTouches() {
-        return mQs.disallowPanelTouches();
+        if (mQs != null) {
+            return mQs.disallowPanelTouches();
+        } else {
+            return false;
+        }
     }
 
     void setListening(boolean listening) {
@@ -1022,7 +1026,6 @@
                 && mPanelViewControllerLazy.get().mAnimateBack) {
             mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction);
         }
-        mShadeExpansionStateManager.onQsExpansionFractionChanged(qsExpansionFraction);
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
         int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 0554c58..9493989 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -36,10 +36,7 @@
 class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
-    private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>()
     private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
-    private val qsExpansionFractionListeners =
-        CopyOnWriteArrayList<ShadeQsExpansionFractionListener>()
     private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
     private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
 
@@ -67,15 +64,6 @@
         expansionListeners.remove(listener)
     }
 
-    fun addFullExpansionListener(listener: ShadeFullExpansionListener) {
-        fullExpansionListeners.add(listener)
-        listener.onShadeExpansionFullyChanged(qsExpanded)
-    }
-
-    fun removeFullExpansionListener(listener: ShadeFullExpansionListener) {
-        fullExpansionListeners.remove(listener)
-    }
-
     fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
         qsExpansionListeners.add(listener)
         listener.onQsExpansionChanged(qsExpanded)
@@ -85,25 +73,11 @@
         qsExpansionListeners.remove(listener)
     }
 
-    fun addQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
-        qsExpansionFractionListeners.add(listener)
-        listener.onQsExpansionFractionChanged(qsExpansionFraction)
-    }
-
-    fun removeQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
-        qsExpansionFractionListeners.remove(listener)
-    }
-
     /** Adds a listener that will be notified when the panel state has changed. */
     fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
     }
 
-    /** Removes a state listener. */
-    fun removeStateListener(listener: ShadeStateListener) {
-        stateListeners.remove(listener)
-    }
-
     override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
         shadeStateEventsListeners.addIfAbsent(listener)
     }
@@ -187,22 +161,6 @@
         qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
     }
 
-    fun onQsExpansionFractionChanged(qsExpansionFraction: Float) {
-        this.qsExpansionFraction = qsExpansionFraction
-
-        debugLog("qsExpansionFraction=$qsExpansionFraction")
-        qsExpansionFractionListeners.forEach {
-            it.onQsExpansionFractionChanged(qsExpansionFraction)
-        }
-    }
-
-    fun onShadeExpansionFullyChanged(isExpanded: Boolean) {
-        this.expanded = isExpanded
-
-        debugLog("expanded=$isExpanded")
-        fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) }
-    }
-
     /** Updates the panel state if necessary. */
     fun updateState(@PanelState state: Int) {
         debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
deleted file mode 100644
index 6d13e19..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
+++ /dev/null
@@ -1,23 +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.shade
-
-/** A listener interface to be notified of expansion events for the notification shade. */
-fun interface ShadeFullExpansionListener {
-    /** Invoked whenever the shade expansion changes, when it is fully collapsed or expanded */
-    fun onShadeExpansionFullyChanged(isExpanded: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 25bd8e7..cb95b25 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -49,6 +48,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
@@ -304,7 +304,8 @@
 
         iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
         iconManager.setTint(
-            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary),
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse),
         )
 
         carrierIconSlots =
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 6117f9f..f043c71 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
@@ -34,8 +34,7 @@
 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.user.domain.interactor.UserInteractor
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
@@ -71,7 +70,7 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     powerInteractor: PowerInteractor,
     userSetupRepository: UserSetupRepository,
-    userInteractor: UserInteractor,
+    userSwitcherInteractor: UserSwitcherInteractor,
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) {
@@ -140,13 +139,6 @@
     /** Whether either the shade or QS is fully expanded. */
     val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
 
-    /** Whether either the shade or QS is expanding from a fully collapsed state. */
-    val isAnyExpanding: Flow<Boolean> =
-        anyExpansion
-            .pairwise(1f)
-            .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
-            .distinctUntilChanged()
-
     /**
      * Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At
      * this time, this is not simply a matter of checking if either value in shadeExpansion and
@@ -156,12 +148,13 @@
      *
      * TODO(b/300258424) remove all but the first sentence of this comment
      */
-    val isAnyExpanded: Flow<Boolean> =
+    val isAnyExpanded: StateFlow<Boolean> =
         if (sceneContainerFlags.isEnabled()) {
-            anyExpansion.map { it > 0f }.distinctUntilChanged()
-        } else {
-            repository.legacyExpandedOrAwaitingInputTransfer
-        }
+                anyExpansion.map { it > 0f }.distinctUntilChanged()
+            } else {
+                repository.legacyExpandedOrAwaitingInputTransfer
+            }
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
     /**
      * Whether the user is expanding or collapsing the shade with user input. This will be true even
@@ -192,7 +185,7 @@
      * but a transition they initiated is still animating.
      */
     val isUserInteracting: Flow<Boolean> =
-        combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs }
+        combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
             .distinctUntilChanged()
 
     /** Are touches allowed on the notification panel? */
@@ -227,7 +220,7 @@
             isDeviceProvisioned &&
                 // Disallow QS during setup if it's a simple user switcher. (The user intends to
                 // use the lock screen user switcher, QS is not needed.)
-                (isUserSetup || !userInteractor.isSimpleUserSwitcher) &&
+                (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) &&
                 isShadeEnabled &&
                 disableFlags.isQuickSettingsEnabled() &&
                 !isDozing
@@ -261,7 +254,7 @@
                 when (state) {
                     is ObservableTransitionState.Idle -> false
                     is ObservableTransitionState.Transition ->
-                        state.isInitiatedByUserInput &&
+                        state.isUserInputDriven &&
                             (state.toScene == sceneKey || state.fromScene == sceneKey)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java b/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
deleted file mode 100644
index f1eb9fe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
+++ /dev/null
@@ -1,65 +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.systemui.statusbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-/**
- * A view who contains media artwork.
- */
-public class BackDropView extends FrameLayout
-{
-    private Runnable mOnVisibilityChangedRunnable;
-
-    public BackDropView(Context context) {
-        super(context);
-    }
-
-    public BackDropView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public BackDropView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public BackDropView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        if (changedView == this && mOnVisibilityChangedRunnable != null) {
-            mOnVisibilityChangedRunnable.run();
-        }
-    }
-
-    public void setOnVisibilityChangedRunnable(Runnable runnable) {
-        mOnVisibilityChangedRunnable = runnable;
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 77b0958..c8669072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar;
 
+import android.animation.Animator;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 
 import androidx.annotation.Nullable;
 
@@ -31,30 +33,42 @@
     public static final long ANIMATION_DURATION_LENGTH = 210;
 
     public static void fadeOut(final View view) {
-        fadeOut(view, null);
+        fadeOut(view, (Runnable) null);
     }
 
     public static void fadeOut(final View view, final Runnable endRunnable) {
         fadeOut(view, ANIMATION_DURATION_LENGTH, 0, endRunnable);
     }
 
+    public static void fadeOut(final View view, final Animator.AnimatorListener listener) {
+        fadeOut(view, ANIMATION_DURATION_LENGTH, 0, listener);
+    }
+
+    public static void fadeOut(final View view, long duration, int delay) {
+        fadeOut(view, duration, delay, (Runnable) null);
+    }
+
+    /**
+     * Perform a fade-out animation, invoking {@code endRunnable} when the animation ends. It will
+     * not be invoked if the animation is cancelled.
+     *
+     * @deprecated Use {@link #fadeOut(View, long, int, Animator.AnimatorListener)} instead.
+     */
+    @Deprecated
     public static void fadeOut(final View view, long duration, int delay,
-            final Runnable endRunnable) {
+            @Nullable final Runnable endRunnable) {
         view.animate().cancel();
         view.animate()
                 .alpha(0f)
                 .setDuration(duration)
                 .setInterpolator(Interpolators.ALPHA_OUT)
                 .setStartDelay(delay)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (endRunnable != null) {
-                            endRunnable.run();
-                        }
-                        if (view.getVisibility() != View.GONE) {
-                            view.setVisibility(View.INVISIBLE);
-                        }
+                .withEndAction(() -> {
+                    if (endRunnable != null) {
+                        endRunnable.run();
+                    }
+                    if (view.getVisibility() != View.GONE) {
+                        view.setVisibility(View.INVISIBLE);
                     }
                 });
         if (view.hasOverlappingRendering()) {
@@ -62,6 +76,27 @@
         }
     }
 
+    public static void fadeOut(final View view, long duration, int delay,
+            @Nullable final Animator.AnimatorListener listener) {
+        view.animate().cancel();
+        ViewPropertyAnimator animator = view.animate()
+                .alpha(0f)
+                .setDuration(duration)
+                .setInterpolator(Interpolators.ALPHA_OUT)
+                .setStartDelay(delay)
+                .withEndAction(() -> {
+                    if (view.getVisibility() != View.GONE) {
+                        view.setVisibility(View.INVISIBLE);
+                    }
+                });
+        if (listener != null) {
+            animator.setListener(listener);
+        }
+        if (view.hasOverlappingRendering()) {
+            view.animate().withLayer();
+        }
+    }
+
     public static void fadeOut(View view, float fadeOutAmount) {
         fadeOut(view, fadeOutAmount, true /* remap */);
     }
@@ -119,10 +154,21 @@
         fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, endRunnable);
     }
 
-    public static void fadeIn(final View view, long duration, int delay) {
-        fadeIn(view, duration, delay, /* endRunnable= */ null);
+    public static void fadeIn(final View view, Animator.AnimatorListener listener) {
+        fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, listener);
     }
 
+    public static void fadeIn(final View view, long duration, int delay) {
+        fadeIn(view, duration, delay, /* endRunnable= */ (Runnable) null);
+    }
+
+    /**
+     * Perform a fade-in animation, invoking {@code endRunnable} when the animation ends. It will
+     * not be invoked if the animation is cancelled.
+     *
+     * @deprecated Use {@link #fadeIn(View, long, int, Animator.AnimatorListener)} instead.
+     */
+    @Deprecated
     public static void fadeIn(final View view, long duration, int delay,
             @Nullable Runnable endRunnable) {
         view.animate().cancel();
@@ -141,6 +187,26 @@
         }
     }
 
+    public static void fadeIn(final View view, long duration, int delay,
+            @Nullable Animator.AnimatorListener listener) {
+        view.animate().cancel();
+        if (view.getVisibility() == View.INVISIBLE) {
+            view.setAlpha(0.0f);
+            view.setVisibility(View.VISIBLE);
+        }
+        ViewPropertyAnimator animator = view.animate()
+                .alpha(1f)
+                .setDuration(duration)
+                .setStartDelay(delay)
+                .setInterpolator(Interpolators.ALPHA_IN);
+        if (listener != null) {
+            animator.setListener(listener);
+        }
+        if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+            view.animate().withLayer();
+        }
+    }
+
     public static void fadeIn(View view, float fadeInAmount) {
         fadeIn(view, fadeInAmount, false /* remap */);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 2f1b589..dd24ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1457,7 +1457,7 @@
     }
 
     private boolean canUnlockWithFingerprint() {
-        return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+        return mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
                 getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
deleted file mode 100644
index 17b4e3b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Point
-import android.graphics.Rect
-import android.renderscript.Allocation
-import android.renderscript.Element
-import android.renderscript.RenderScript
-import android.renderscript.ScriptIntrinsicBlur
-import android.util.Log
-import android.util.MathUtils
-import com.android.internal.graphics.ColorUtils
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-import javax.inject.Inject
-
-private const val TAG = "MediaArtworkProcessor"
-private const val COLOR_ALPHA = (255 * 0.7f).toInt()
-private const val BLUR_RADIUS = 25f
-private const val DOWNSAMPLE = 6
-
-@SysUISingleton
-class MediaArtworkProcessor @Inject constructor() {
-
-    private val mTmpSize = Point()
-    private var mArtworkCache: Bitmap? = null
-
-    fun processArtwork(context: Context, artwork: Bitmap): Bitmap? {
-        if (mArtworkCache != null) {
-            return mArtworkCache
-        }
-        val renderScript = RenderScript.create(context)
-        val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
-        var input: Allocation? = null
-        var output: Allocation? = null
-        var inBitmap: Bitmap? = null
-        try {
-            @Suppress("DEPRECATION")
-            context.display?.getSize(mTmpSize)
-            val rect = Rect(0, 0, artwork.width, artwork.height)
-            MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
-            inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
-                    true /* filter */)
-            // Render script blurs only support ARGB_8888, we need a conversion if we got a
-            // different bitmap config.
-            if (inBitmap.config != Bitmap.Config.ARGB_8888) {
-                val oldIn = inBitmap
-                inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
-                oldIn.recycle()
-            }
-            val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
-                    Bitmap.Config.ARGB_8888)
-
-            input = Allocation.createFromBitmap(renderScript, inBitmap,
-                    Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
-            output = Allocation.createFromBitmap(renderScript, outBitmap)
-
-            blur.setRadius(BLUR_RADIUS)
-            blur.setInput(input)
-            blur.forEach(output)
-            output.copyTo(outBitmap)
-
-            val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
-
-            val canvas = Canvas(outBitmap)
-            canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA))
-            return outBitmap
-        } catch (ex: IllegalArgumentException) {
-            Log.e(TAG, "error while processing artwork", ex)
-            return null
-        } finally {
-            input?.destroy()
-            output?.destroy()
-            blur.destroy()
-            inBitmap?.recycle()
-        }
-    }
-
-    fun clearCache() {
-        mArtworkCache?.recycle()
-        mArtworkCache = null
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index ff4570e..49743bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,9 +31,11 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
 import com.android.systemui.statusbar.notification.collection.PipelineDumper;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 import com.android.systemui.util.time.SystemClock;
@@ -60,6 +62,7 @@
 
     private final Context mContext;
     private final NotificationManager mNotificationManager;
+    private final SilentNotificationStatusIconsVisibilityInteractor mStatusIconInteractor;
     private final SystemClock mSystemClock;
     private final Executor mMainExecutor;
     private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
@@ -76,12 +79,14 @@
     public NotificationListener(
             Context context,
             NotificationManager notificationManager,
+            SilentNotificationStatusIconsVisibilityInteractor statusIconInteractor,
             SystemClock systemClock,
             @Main Executor mainExecutor,
             PluginManager pluginManager) {
         super(pluginManager);
         mContext = context;
         mNotificationManager = notificationManager;
+        mStatusIconInteractor = statusIconInteractor;
         mSystemClock = systemClock;
         mMainExecutor = mainExecutor;
     }
@@ -95,7 +100,9 @@
     }
 
     /** Registers a listener that's notified when any notification-related settings change. */
+    @Deprecated
     public void addNotificationSettingsListener(NotificationSettingsListener listener) {
+        NotificationIconContainerRefactor.assertInLegacyMode();
         mSettingsListeners.add(listener);
     }
 
@@ -230,8 +237,12 @@
 
     @Override
     public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
-        for (NotificationSettingsListener listener : mSettingsListeners) {
-            listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
+        } else {
+            for (NotificationSettingsListener listener : mSettingsListeners) {
+                listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+            }
         }
     }
 
@@ -294,6 +305,7 @@
         return ranking;
     }
 
+    @Deprecated
     public interface NotificationSettingsListener {
 
         default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2147510..710e59d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,9 +16,17 @@
 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.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.os.Flags.allowPrivateProfile;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -30,8 +38,10 @@
 import android.content.IntentSender;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -41,14 +51,18 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.recents.OverviewProxyService;
@@ -63,7 +77,10 @@
 
 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 javax.inject.Inject;
 
@@ -84,6 +101,11 @@
     private final SecureSettings mSecureSettings;
     private final Object mLock = new Object();
 
+    private static final Uri SHOW_LOCKSCREEN =
+            Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+    private static final Uri SHOW_PRIVATE_LOCKSCREEN =
+            Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
     private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
     private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -91,6 +113,23 @@
     private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();
     private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
     private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+    // The variables between mUsersDpcAllowingNotifications and
+    // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread
+    // and read on the main thread. Because the pipeline needs these values, adding locks would
+    // introduce too much jank. This means that some pipeline runs could get incorrect values, that
+    // would be fixed on the next pipeline run. We think this will be rare since a pipeline run
+    // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the
+    // pipeline frequently enough that it should be corrected by the next time it matters for the
+    // user.
+    private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray();
+    private boolean mKeyguardAllowingNotifications = true;
+    private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications
+            = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications
+            = new SparseBooleanArray();
+
     private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
     private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
     private final UserManager mUserManager;
@@ -99,24 +138,39 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
     private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
+    private final FeatureFlagsClassic mFeatureFlags;
     private boolean mShowLockscreenNotifications;
     private LockPatternUtils mLockPatternUtils;
     protected KeyguardManager mKeyguardManager;
     private int mState = StatusBarState.SHADE;
     private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =
             new ListenerSet<>();
+    private final Collection<Uri> mLockScreenUris = new ArrayList<>();
+
 
     protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
 
-            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
-                    isCurrentProfile(getSendingUserId())) {
-                mUsersAllowingPrivateNotifications.clear();
-                updateLockscreenNotificationSetting();
-                // TODO(b/231976036): Consolidate pipeline invalidations related to this event
-                // notifyNotificationStateChanged();
+            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    boolean changed = updateDpcSettings(getSendingUserId());
+                    if (mCurrentUserId == getSendingUserId()) {
+                        changed |= updateLockscreenNotificationSetting();
+                    }
+                    if (changed) {
+                        notifyNotificationStateChanged();
+                    }
+                } else {
+                    if (isCurrentProfile(getSendingUserId())) {
+                        mUsersAllowingPrivateNotifications.clear();
+                        updateLockscreenNotificationSetting();
+                        // TODO(b/231976036): Consolidate pipeline invalidations related to this
+                        //  event
+                        // notifyNotificationStateChanged();
+                    }
+                }
             }
         }
     };
@@ -125,49 +179,50 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            switch (action) {
-                case Intent.ACTION_USER_REMOVED:
-                    int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                    if (removedUserId != -1) {
-                        for (UserChangedListener listener : mListeners) {
-                            listener.onUserRemoved(removedUserId);
-                        }
+            if (Objects.equals(action, Intent.ACTION_USER_REMOVED)) {
+                int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (removedUserId != -1) {
+                    for (UserChangedListener listener : mListeners) {
+                        listener.onUserRemoved(removedUserId);
                     }
-                    updateCurrentProfilesCache();
-                    break;
-                case Intent.ACTION_USER_ADDED:
-                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
-                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
-                    updateCurrentProfilesCache();
-                    break;
-                case 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();
+                }
+                updateCurrentProfilesCache();
+            } else if (Objects.equals(action, Intent.ACTION_USER_ADDED)){
+                updateCurrentProfilesCache();
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    mBackgroundHandler.post(() -> {
+                        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();
+                }
+            } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
+                final IntentSender intentSender = intent.getParcelableExtra(
+                        Intent.EXTRA_INTENT);
+                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+                if (intentSender != null) {
+                    try {
+                        ActivityOptions options = ActivityOptions.makeBasic();
+                        options.setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                        mContext.startIntentSender(intentSender, null, 0, 0, 0,
+                                options.toBundle());
+                    } catch (IntentSender.SendIntentException e) {
+                        /* ignore */
                     }
-                    break;
-                case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
-                    final IntentSender intentSender = intent.getParcelableExtra(
-                            Intent.EXTRA_INTENT);
-                    final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
-                    if (intentSender != null) {
-                        try {
-                            ActivityOptions options = ActivityOptions.makeBasic();
-                            options.setPendingIntentBackgroundActivityStartMode(
-                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-                            mContext.startIntentSender(intentSender, null, 0, 0, 0,
-                                    options.toBundle());
-                        } catch (IntentSender.SendIntentException e) {
-                            /* ignore */
-                        }
-                    }
-                    if (notificationKey != null) {
-                        final NotificationVisibility nv = mVisibilityProviderLazy.get()
-                                .obtain(notificationKey, true);
-                        mClickNotifier.onNotificationClick(notificationKey, nv);
-                    }
-                    break;
+                }
+                if (notificationKey != null) {
+                    final NotificationVisibility nv = mVisibilityProviderLazy.get()
+                            .obtain(notificationKey, true);
+                    mClickNotifier.onNotificationClick(notificationKey, nv);
+                }
             }
         }
     };
@@ -193,6 +248,8 @@
 
     protected final Context mContext;
     private final Handler mMainHandler;
+    private final Handler mBackgroundHandler;
+    private final Executor mBackgroundExecutor;
     protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
     protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
 
@@ -214,13 +271,18 @@
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
             @Main Handler mainHandler,
+            @Background Handler backgroundHandler,
+            @Background Executor backgroundExecutor,
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
             SecureSettings secureSettings,
             DumpManager dumpManager,
-            LockPatternUtils lockPatternUtils) {
+            LockPatternUtils lockPatternUtils,
+            FeatureFlagsClassic featureFlags) {
         mContext = context;
         mMainHandler = mainHandler;
+        mBackgroundHandler = backgroundHandler;
+        mBackgroundExecutor = backgroundExecutor;
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
         mUserTracker = userTracker;
@@ -236,6 +298,10 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mSecureSettings = secureSettings;
         mKeyguardStateController = keyguardStateController;
+        mFeatureFlags = featureFlags;
+
+        mLockScreenUris.add(SHOW_LOCKSCREEN);
+        mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
 
         dumpManager.registerDumpable(this);
     }
@@ -243,16 +309,53 @@
     public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
 
-        mLockscreenSettingsObserver = new ContentObserver(mMainHandler) {
+        mLockscreenSettingsObserver = new ContentObserver(
+                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+                        ? mBackgroundHandler
+                        : mMainHandler) {
+
             @Override
-            public void onChange(boolean selfChange) {
-                // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
-                // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
-                mUsersAllowingPrivateNotifications.clear();
-                mUsersAllowingNotifications.clear();
-                // ... and refresh all the notifications
-                updateLockscreenNotificationSetting();
-                notifyNotificationStateChanged();
+            public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    @SuppressLint("MissingPermission")
+                    List<UserInfo> users = mUserManager.getUsers();
+                    for (int i = users.size() - 1; i >= 0; i--) {
+                        onChange(selfChange, uris, flags,users.get(i).getUserHandle());
+                    }
+                } else {
+                    // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+                    // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+                    mUsersAllowingPrivateNotifications.clear();
+                    mUsersAllowingNotifications.clear();
+                    // ... and refresh all the notifications
+                    updateLockscreenNotificationSetting();
+                    notifyNotificationStateChanged();
+                }
+            }
+
+            // Note: even though this is an override, this method is not called by the OS
+            // since we're not in system_server. We are using it internally for cases when
+            // we have a single user id available (e.g. from USER_ADDED).
+            @Override
+            public void onChange(boolean selfChange, Collection<Uri> uris,
+                    int flags, UserHandle user) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    boolean changed = false;
+                    for (Uri uri: uris) {
+                        if (SHOW_LOCKSCREEN.equals(uri)) {
+                            changed |= updateUserShowSettings(user.getIdentifier());
+                        } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+                            changed |= updateUserShowPrivateSettings(user.getIdentifier());
+                        }
+                    }
+
+                    if (mCurrentUserId == user.getIdentifier()) {
+                        changed |= updateLockscreenNotificationSetting();
+                    }
+                    if (changed) {
+                        notifyNotificationStateChanged();
+                    }
+                }
             }
         };
 
@@ -268,23 +371,26 @@
         };
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+                SHOW_LOCKSCREEN, false,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                SHOW_PRIVATE_LOCKSCREEN,
                 true,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
-                mSettingsObserver);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+                    mSettingsObserver);
+        }
 
         mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
                 new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
-                null /* handler */, UserHandle.ALL);
+                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+                        ? mBackgroundExecutor : null, UserHandle.ALL);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
@@ -292,6 +398,10 @@
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        if (allowPrivateProfile()){
+            filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
+            filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
+        }
         mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter,
                 null /* executor */, UserHandle.ALL);
 
@@ -305,7 +415,23 @@
         mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
         updateCurrentProfilesCache();
 
-        mSettingsObserver.onChange(false);  // set up
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // Set  up
+            mBackgroundHandler.post(() -> {
+                @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+                for (int i = users.size() - 1; i >= 0; i--) {
+                    initValuesForUser(users.get(i).id);
+                }
+            });
+        } else {
+            mSettingsObserver.onChange(false);  // set up
+        }
+    }
+
+    private void initValuesForUser(@UserIdInt int userId) {
+        mLockscreenSettingsObserver.onChange(
+                false, mLockScreenUris, 0, UserHandle.of(userId));
+        updateDpcSettings(userId);
     }
 
     public boolean shouldShowLockscreenNotifications() {
@@ -322,17 +448,75 @@
         mShowLockscreenNotifications = show;
     }
 
-    protected void updateLockscreenNotificationSetting() {
-        final boolean show = mSecureSettings.getIntForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                1,
-                mCurrentUserId) != 0;
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
-                null /* admin */, mCurrentUserId);
-        final boolean allowedByDpm = (dpmFlags
-                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+    protected boolean updateLockscreenNotificationSetting() {
+        boolean show;
+        boolean allowedByDpm;
 
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+                    && mKeyguardAllowingNotifications;
+            // If DPC never notified us about a user, that means they have no policy for the user,
+            // and they allow the behavior
+            allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+        } else {
+            show = mSecureSettings.getIntForUser(
+                    LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                    1,
+                    mCurrentUserId) != 0;
+            final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                    null /* admin */, mCurrentUserId);
+            allowedByDpm = (dpmFlags
+                    & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+        }
+
+        final boolean oldValue = mShowLockscreenNotifications;
         setShowLockscreenNotifications(show && allowedByDpm);
+
+        return oldValue != mShowLockscreenNotifications;
+    }
+
+    @WorkerThread
+    protected boolean updateDpcSettings(int userId) {
+        boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId);
+        boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId);
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                null /* admin */, userId);
+        final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+        final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+        mUsersDpcAllowingNotifications.put(userId, allowedLockscreen);
+        mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate);
+        return (originalAllowLockscreen != allowedLockscreen)
+                || (originalAllowPrivate != allowedPrivate);
+    }
+
+    @WorkerThread
+    private boolean updateUserShowSettings(int userId) {
+        boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId);
+        boolean newAllowLockscreen = mSecureSettings.getIntForUser(
+                LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                1,
+                userId) != 0;
+        mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
+        boolean keyguardChanged = updateGlobalKeyguardSettings();
+        return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+    }
+
+    @WorkerThread
+    private boolean updateUserShowPrivateSettings(int userId) {
+        boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId);
+        boolean newValue = mSecureSettings.getIntForUser(
+                LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                0,
+                userId) != 0;
+        mUsersUsersAllowingPrivateNotifications.put(userId, newValue);
+        return (newValue != originalValue);
+    }
+
+    @WorkerThread
+    private boolean updateGlobalKeyguardSettings() {
+        final boolean oldValue = mKeyguardAllowingNotifications;
+        mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
+        return oldValue != mKeyguardAllowingNotifications;
     }
 
     /**
@@ -340,21 +524,41 @@
      * when the lockscreen is in "public" (secure & locked) mode?
      */
     public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            if (userHandle == UserHandle.USER_ALL) {
+                userHandle = mCurrentUserId;
+            }
+            if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
+                updateUserShowPrivateSettings(userHandle);
+            }
+            if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+                updateDpcSettings(userHandle);
+            }
+            return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+                    && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+        } else {
+            if (userHandle == UserHandle.USER_ALL) {
+                return true;
+            }
 
-        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
-            return allowed;
-        }
+            if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+                        LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                        KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+                final boolean allowed = allowedByUser && allowedByDpm;
+                mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+                return allowed;
+            }
 
-        return mUsersAllowingPrivateNotifications.get(userHandle);
+            return mUsersAllowingPrivateNotifications.get(userHandle);
+        }
     }
 
     /**
@@ -406,21 +610,44 @@
      * "public" (secure & locked) mode?
      */
     public boolean userAllowsNotificationsInPublic(int userHandle) {
-        if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
-            return true;
-        }
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // Unlike 'show private', settings does not show a copy of this setting for each
+            // profile, so it inherits from the parent user.
+            if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+                userHandle = mCurrentUserId;
+            }
+            if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+                updateUserShowSettings(userHandle);
+            }
+            if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+                updateDpcSettings(userHandle);
+            }
+            return mUsersUsersAllowingNotifications.get(userHandle)
+                    && mUsersDpcAllowingNotifications.get(userHandle)
+                    && mKeyguardAllowingNotifications;
+        } else {
+            if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
+                return true;
+            }
 
-        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-            final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
-            final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
-            mUsersAllowingNotifications.append(userHandle, allowed);
-            return allowed;
+            if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+                        LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                        KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+                final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
+                final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
+                mUsersAllowingNotifications.append(userHandle, allowed);
+                return allowed;
+            }
+            return mUsersAllowingNotifications.get(userHandle);
         }
-        return mUsersAllowingNotifications.get(userHandle);
     }
 
     /** @return true if the entry needs redaction when on the lockscreen. */
@@ -451,9 +678,15 @@
             return true;
         }
         NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
-        return entry != null
-                && entry.getRanking().getLockscreenVisibilityOverride() 
-                == Notification.VISIBILITY_PRIVATE;
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            return entry != null
+                    && entry.getRanking().getChannel().getLockscreenVisibility()
+                    == Notification.VISIBILITY_PRIVATE;
+        } else {
+            return entry != null
+                    && entry.getRanking().getLockscreenVisibilityOverride()
+                    == Notification.VISIBILITY_PRIVATE;
+        }
     }
 
     private void updateCurrentProfilesCache() {
@@ -491,20 +724,6 @@
     }
 
     /**
-     * If any managed/work profiles are in public mode.
-     */
-    public boolean isAnyManagedProfilePublicMode() {
-        synchronized (mLock) {
-            for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
-                if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns the current user id. This can change if the user is switched.
      */
     public int getCurrentUserId() {
@@ -581,11 +800,27 @@
     }
 
     private void notifyNotificationStateChanged() {
-        for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
-            listener.onNotificationStateChanged();
+        if (!Looper.getMainLooper().isCurrentThread()) {
+            mMainHandler.post(() -> {
+                for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                    listener.onNotificationStateChanged();
+                }
+            });
+        } else {
+            for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                listener.onNotificationStateChanged();
+            }
         }
     }
 
+    private boolean profileAvailabilityActions(String action){
+        return allowPrivateProfile()?
+                Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
+                        Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
+                Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
+                        Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
@@ -620,5 +855,15 @@
         pw.println(mUsersInLockdownLatestResult);
         pw.print("  mShouldHideNotifsLatestResult=");
         pw.println(mShouldHideNotifsLatestResult);
+        pw.print("  mUsersDpcAllowingNotifications=");
+        pw.println(mUsersDpcAllowingNotifications);
+        pw.print("  mUsersUsersAllowingNotifications=");
+        pw.println(mUsersUsersAllowingNotifications);
+        pw.print("  mKeyguardAllowingNotifications=");
+        pw.println(mKeyguardAllowingNotifications);
+        pw.print("  mUsersDpcAllowingPrivateNotifications=");
+        pw.println(mUsersDpcAllowingPrivateNotifications);
+        pw.print("  mUsersUsersAllowingPrivateNotifications=");
+        pw.println(mUsersUsersAllowingPrivateNotifications);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5bd40b8..9c4625e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,48 +15,24 @@
  */
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
-
-import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
-import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.hardware.display.DisplayManager;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
-import android.os.AsyncTask;
-import android.os.Trace;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import android.widget.ImageView;
 
-import com.android.app.animation.Interpolators;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.models.player.MediaData;
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -64,29 +40,13 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ScrimState;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import dagger.Lazy;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Handles tasks and state related to media notifications. For example, there is a 'current' media
@@ -96,10 +56,6 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final StatusBarStateController mStatusBarStateController;
-    private final SysuiColorExtractor mColorExtractor;
-    private final KeyguardStateController mKeyguardStateController;
-    private final KeyguardBypassController mKeyguardBypassController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
     private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
     static {
@@ -116,42 +72,14 @@
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
 
-    @Nullable
-    private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
-
-    @Nullable
-    private BiometricUnlockController mBiometricUnlockController;
-    @Nullable
-    private ScrimController mScrimController;
-    @Nullable
-    private LockscreenWallpaper mLockscreenWallpaper;
-    @VisibleForTesting
-    boolean mIsLockscreenLiveWallpaperEnabled;
-
-    private final DelayableExecutor mMainExecutor;
-
     private final Context mContext;
     private final ArrayList<MediaListener> mMediaListeners;
-    private final MediaArtworkProcessor mMediaArtworkProcessor;
-    private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
 
     protected NotificationPresenter mPresenter;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
 
-    private BackDropView mBackdrop;
-    private ImageView mBackdropFront;
-    private ImageView mBackdropBack;
-    private final Point mTmpDisplaySize = new Point();
-
-    private final DisplayManager mDisplayManager;
-    @Nullable
-    private List<String> mSmallerInternalDisplayUids;
-    private Display mCurrentDisplay;
-
-    private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable;
-
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -173,9 +101,8 @@
             if (DEBUG_MEDIA) {
                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
             }
-            mMediaArtworkProcessor.clearCache();
             mMediaMetadata = metadata;
-            dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
+            dispatchUpdateMediaMetaData();
         }
     };
 
@@ -184,35 +111,17 @@
      */
     public NotificationMediaManager(
             Context context,
-            Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationVisibilityProvider visibilityProvider,
-            MediaArtworkProcessor mediaArtworkProcessor,
-            KeyguardBypassController keyguardBypassController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
-            @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
-            StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor,
-            KeyguardStateController keyguardStateController,
-            DumpManager dumpManager,
-            WallpaperManager wallpaperManager,
-            DisplayManager displayManager) {
+            DumpManager dumpManager) {
         mContext = context;
-        mMediaArtworkProcessor = mediaArtworkProcessor;
-        mKeyguardBypassController = keyguardBypassController;
         mMediaListeners = new ArrayList<>();
-        mNotificationShadeWindowController = notificationShadeWindowController;
         mVisibilityProvider = visibilityProvider;
-        mMainExecutor = mainExecutor;
         mMediaDataManager = mediaDataManager;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
-        mStatusBarStateController = statusBarStateController;
-        mColorExtractor = colorExtractor;
-        mKeyguardStateController = keyguardStateController;
-        mDisplayManager = displayManager;
-        mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled();
 
         setupNotifPipeline();
 
@@ -316,7 +225,7 @@
     public void onNotificationRemoved(String key) {
         if (key.equals(mMediaNotificationKey)) {
             clearCurrentMediaNotification();
-            dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */);
+            dispatchUpdateMediaMetaData();
         }
     }
 
@@ -350,21 +259,18 @@
     }
 
     public void findAndUpdateMediaNotifications() {
-        boolean metaDataChanged;
         // TODO(b/169655907): get the semi-filtered notifications for current user
         Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
-        metaDataChanged = findPlayingMediaNotification(allNotifications);
-        dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+        findPlayingMediaNotification(allNotifications);
+        dispatchUpdateMediaMetaData();
     }
 
     /**
      * Find a notification and media controller associated with the playing media session, and
      * update this manager's internal state.
-     * @return whether the current MediaMetadata changed (and needs to be announced to listeners).
+     * TODO(b/273443374) check this method
      */
-    boolean findPlayingMediaNotification(
-            @NonNull Collection<NotificationEntry> allNotifications) {
-        boolean metaDataChanged = false;
+    void findPlayingMediaNotification(@NonNull Collection<NotificationEntry> allNotifications) {
         // Promote the media notification with a controller in 'playing' state, if any.
         NotificationEntry mediaNotification = null;
         MediaController controller = null;
@@ -400,8 +306,6 @@
                 Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
                         + mMediaController + ", receive metadata: " + mMediaMetadata);
             }
-
-            metaDataChanged = true;
         }
 
         if (mediaNotification != null
@@ -412,8 +316,6 @@
                         + mMediaNotificationKey);
             }
         }
-
-        return metaDataChanged;
     }
 
     public void clearCurrentMediaNotification() {
@@ -421,10 +323,7 @@
         clearCurrentMediaNotificationSession();
     }
 
-    private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) {
-        if (mPresenter != null) {
-            mPresenter.updateMediaMetaData(changed, allowEnterAnimation);
-        }
+    private void dispatchUpdateMediaMetaData() {
         @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
         ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
         for (int i = 0; i < callbacks.size(); i++) {
@@ -476,7 +375,6 @@
     }
 
     private void clearCurrentMediaNotificationSession() {
-        mMediaArtworkProcessor.clearCache();
         mMediaMetadata = null;
         if (mMediaController != null) {
             if (DEBUG_MEDIA) {
@@ -488,344 +386,6 @@
         mMediaController = null;
     }
 
-    /**
-     * Notify lockscreen wallpaper drawable the current internal display.
-     */
-    public void onDisplayUpdated(Display display) {
-        Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
-        mCurrentDisplay = display;
-        if (mWallapperDrawable != null) {
-            mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays());
-        }
-        Trace.endSection();
-    }
-
-    private boolean isOnSmallerInternalDisplays() {
-        if (mSmallerInternalDisplayUids == null) {
-            mSmallerInternalDisplayUids = findSmallerInternalDisplayUids();
-        }
-        return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId());
-    }
-
-    private List<String> findSmallerInternalDisplayUids() {
-        if (mSmallerInternalDisplayUids != null) {
-            return mSmallerInternalDisplayUids;
-        }
-        List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays(
-                        DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
-                .filter(display -> display.getType() == Display.TYPE_INTERNAL)
-                .collect(Collectors.toList());
-        if (internalDisplays.isEmpty()) {
-            return List.of();
-        }
-        Display largestDisplay = internalDisplays.stream()
-                .max(Comparator.comparingInt(this::getRealDisplayArea))
-                .orElse(internalDisplays.get(0));
-        internalDisplays.remove(largestDisplay);
-        return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList());
-    }
-
-    private int getRealDisplayArea(Display display) {
-        display.getRealSize(mTmpDisplaySize);
-        return mTmpDisplaySize.x * mTmpDisplaySize.y;
-    }
-
-    /**
-     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
-     */
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-
-        if (mIsLockscreenLiveWallpaperEnabled) return;
-
-        Trace.beginSection("CentralSurfaces#updateMediaMetaData");
-        if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
-            Trace.endSection();
-            return;
-        }
-
-        if (getBackDropView() == null) {
-            Trace.endSection();
-            return; // called too early
-        }
-
-        boolean wakeAndUnlock = mBiometricUnlockController != null
-            && mBiometricUnlockController.isWakeAndUnlock();
-        if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
-            mBackdrop.setVisibility(View.INVISIBLE);
-            Trace.endSection();
-            return;
-        }
-
-        MediaMetadata mediaMetadata = getMediaMetadata();
-
-        if (DEBUG_MEDIA) {
-            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
-                    + getMediaNotificationKey()
-                    + " metadata=" + mediaMetadata
-                    + " metaDataChanged=" + metaDataChanged
-                    + " state=" + mStatusBarStateController.getState());
-        }
-
-        Bitmap artworkBitmap = null;
-        if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {
-            artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
-            if (artworkBitmap == null) {
-                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-            }
-        }
-
-        // Process artwork on a background thread and send the resulting bitmap to
-        // finishUpdateMediaMetaData.
-        if (metaDataChanged) {
-            for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
-                task.cancel(true);
-            }
-            mProcessArtworkTasks.clear();
-        }
-        if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
-            mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
-                    allowEnterAnimation).execute(artworkBitmap));
-        } else {
-            finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
-        }
-
-        Trace.endSection();
-    }
-
-    private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
-            @Nullable Bitmap bmp) {
-        Drawable artworkDrawable = null;
-        if (bmp != null) {
-            artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
-        }
-        boolean hasMediaArtwork = artworkDrawable != null;
-        boolean allowWhenShade = false;
-        if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
-            Bitmap lockWallpaper =
-                    mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
-            if (lockWallpaper != null) {
-                artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
-                        mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays());
-                // We're in the SHADE mode on the SIM screen - yet we still need to show
-                // the lockscreen wallpaper in that mode.
-                allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
-            }
-        }
-
-        NotificationShadeWindowController windowController =
-                mNotificationShadeWindowController.get();
-        boolean hideBecauseOccluded = mKeyguardStateController.isOccluded();
-
-        final boolean hasArtwork = artworkDrawable != null;
-        mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
-        if (mScrimController != null) {
-            mScrimController.setHasBackdrop(hasArtwork);
-        }
-
-        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
-                && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
-                &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()
-                        != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                && !hideBecauseOccluded) {
-            // time to show some art!
-            if (mBackdrop.getVisibility() != View.VISIBLE) {
-                mBackdrop.setVisibility(View.VISIBLE);
-                if (allowEnterAnimation) {
-                    mBackdrop.setAlpha(0);
-                    mBackdrop.animate().alpha(1f);
-                } else {
-                    mBackdrop.animate().cancel();
-                    mBackdrop.setAlpha(1f);
-                }
-                if (windowController != null) {
-                    windowController.setBackdropShowing(true);
-                }
-                metaDataChanged = true;
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
-                }
-            }
-            if (metaDataChanged) {
-                if (mBackdropBack.getDrawable() != null) {
-                    Drawable drawable =
-                            mBackdropBack.getDrawable().getConstantState()
-                                    .newDrawable(mBackdropFront.getResources()).mutate();
-                    mBackdropFront.setImageDrawable(drawable);
-                    mBackdropFront.setAlpha(1f);
-                    mBackdropFront.setVisibility(View.VISIBLE);
-                } else {
-                    mBackdropFront.setVisibility(View.INVISIBLE);
-                }
-
-                if (DEBUG_MEDIA_FAKE_ARTWORK) {
-                    final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
-                    Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
-                    mBackdropBack.setBackgroundColor(0xFFFFFFFF);
-                    mBackdropBack.setImageDrawable(new ColorDrawable(c));
-                } else {
-                    if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) {
-                        mWallapperDrawable =
-                                (LockscreenWallpaper.WallpaperDrawable) artworkDrawable;
-                    }
-                    mBackdropBack.setImageDrawable(artworkDrawable);
-                }
-
-                if (mBackdropFront.getVisibility() == View.VISIBLE) {
-                    if (DEBUG_MEDIA) {
-                        Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
-                                + mBackdropFront.getDrawable()
-                                + " to "
-                                + mBackdropBack.getDrawable());
-                    }
-                    mBackdropFront.animate()
-                            .setDuration(250)
-                            .alpha(0f).withEndAction(mHideBackdropFront);
-                }
-            }
-        } else {
-            // need to hide the album art, either because we are unlocked, on AOD
-            // or because the metadata isn't there to support it
-            if (mBackdrop.getVisibility() != View.GONE) {
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
-                }
-                boolean cannotAnimateDoze = mStatusBarStateController.isDozing()
-                        && !ScrimState.AOD.getAnimateChange();
-                if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode()
-                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                                || cannotAnimateDoze))
-                        || hideBecauseOccluded) {
-                    // We are unlocking directly - no animation!
-                    mBackdrop.setVisibility(View.GONE);
-                    mBackdropBack.setImageDrawable(null);
-                    if (windowController != null) {
-                        windowController.setBackdropShowing(false);
-                    }
-                } else {
-                    if (windowController != null) {
-                        windowController.setBackdropShowing(false);
-                    }
-                    mBackdrop.animate()
-                            .alpha(0)
-                            .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
-                            .setDuration(300)
-                            .setStartDelay(0)
-                            .withEndAction(() -> {
-                                mBackdrop.setVisibility(View.GONE);
-                                mBackdropFront.animate().cancel();
-                                mBackdropBack.setImageDrawable(null);
-                                mMainExecutor.execute(mHideBackdropFront);
-                            });
-                    if (mKeyguardStateController.isKeyguardFadingAway()) {
-                        mBackdrop.animate()
-                                .setDuration(
-                                        mKeyguardStateController.getShortenedFadingAwayDuration())
-                                .setStartDelay(
-                                        mKeyguardStateController.getKeyguardFadingAwayDelay())
-                                .setInterpolator(Interpolators.LINEAR)
-                                .start();
-                    }
-                }
-            }
-        }
-    }
-
-    public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
-            ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
-        mBackdrop = backdrop;
-        mBackdropFront = backdropFront;
-        mBackdropBack = backdropBack;
-        mScrimController = scrimController;
-        mLockscreenWallpaper = lockscreenWallpaper;
-    }
-
-    public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) {
-        mBiometricUnlockController = biometricUnlockController;
-    }
-
-    /**
-     * Hide the album artwork that is fading out and release its bitmap.
-     */
-    protected final Runnable mHideBackdropFront = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG_MEDIA) {
-                Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
-            }
-            mBackdropFront.setVisibility(View.INVISIBLE);
-            mBackdropFront.animate().cancel();
-            mBackdropFront.setImageDrawable(null);
-        }
-    };
-
-    private Bitmap processArtwork(Bitmap artwork) {
-        return mMediaArtworkProcessor.processArtwork(mContext, artwork);
-    }
-
-    @MainThread
-    private void removeTask(AsyncTask<?, ?, ?> task) {
-        mProcessArtworkTasks.remove(task);
-    }
-
-    // TODO(b/273443374): remove
-    public boolean isLockscreenWallpaperOnNotificationShade() {
-        return mBackdrop != null && mLockscreenWallpaper != null
-                && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled()
-                && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser());
-    }
-
-    // TODO(b/273443374) temporary test helper; remove
-    @VisibleForTesting
-    BackDropView getBackDropView() {
-        return mBackdrop;
-    }
-
-    /**
-     * {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
-     */
-    private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> {
-
-        private final WeakReference<NotificationMediaManager> mManagerRef;
-        private final boolean mMetaDataChanged;
-        private final boolean mAllowEnterAnimation;
-
-        ProcessArtworkTask(NotificationMediaManager manager, boolean changed,
-                boolean allowAnimation) {
-            mManagerRef = new WeakReference<>(manager);
-            mMetaDataChanged = changed;
-            mAllowEnterAnimation = allowAnimation;
-        }
-
-        @Override
-        protected Bitmap doInBackground(Bitmap... bitmaps) {
-            NotificationMediaManager manager = mManagerRef.get();
-            if (manager == null || bitmaps.length == 0 || isCancelled()) {
-                return null;
-            }
-            return manager.processArtwork(bitmaps[0]);
-        }
-
-        @Override
-        protected void onPostExecute(@Nullable Bitmap result) {
-            NotificationMediaManager manager = mManagerRef.get();
-            if (manager != null && !isCancelled()) {
-                manager.removeTask(this);
-                manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result);
-            }
-        }
-
-        @Override
-        protected void onCancelled(Bitmap result) {
-            if (result != null) {
-                result.recycle();
-            }
-            NotificationMediaManager manager = mManagerRef.get();
-            if (manager != null) {
-                manager.removeTask(this);
-            }
-        }
-    }
-
     public interface MediaListener {
         /**
          * Called whenever there's new metadata or playback state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 5dcf6d1..f3b5ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -32,11 +32,6 @@
     boolean isPresenterFullyCollapsed();
 
     /**
-     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
-     */
-    void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
-
-    /**
      * Called when the current user changes.
      * @param newUserId new user id
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index d4b6dfb..5f0b298 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar;
 
+
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
@@ -47,11 +48,13 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
-import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dagger.SysUISingleton;
 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.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -64,6 +67,7 @@
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -71,13 +75,16 @@
 import java.util.Objects;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+
 /**
  * Class for handling remote input state over a set of notifications. This class handles things
  * like keeping notifications temporarily that were cancelled as a response to a remote input
  * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
  * and handling clicks on remote views.
  */
-public class NotificationRemoteInputManager implements Dumpable {
+@SysUISingleton
+public class NotificationRemoteInputManager implements CoreStartable {
     public static final boolean ENABLE_REMOTE_INPUT =
             SystemProperties.getBoolean("debug.enable_remote_input", true);
     public static boolean FORCE_REMOTE_INPUT_HISTORY =
@@ -93,6 +100,8 @@
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final PowerInteractor mPowerInteractor;
     private final ActionClickLogger mLogger;
+    private final JavaAdapter mJavaAdapter;
+    private final ShadeInteractor mShadeInteractor;
     protected final Context mContext;
     protected final NotifPipelineFlags mNotifPipelineFlags;
     private final UserManager mUserManager;
@@ -248,6 +257,7 @@
     /**
      * Injected constructor. See {@link CentralSurfacesDependenciesModule}.
      */
+    @Inject
     public NotificationRemoteInputManager(
             Context context,
             NotifPipelineFlags notifPipelineFlags,
@@ -260,7 +270,8 @@
             RemoteInputControllerLogger remoteInputControllerLogger,
             NotificationClickNotifier clickNotifier,
             ActionClickLogger logger,
-            DumpManager dumpManager) {
+            JavaAdapter javaAdapter,
+            ShadeInteractor shadeInteractor) {
         mContext = context;
         mNotifPipelineFlags = notifPipelineFlags;
         mLockscreenUserManager = lockscreenUserManager;
@@ -268,6 +279,8 @@
         mVisibilityProvider = visibilityProvider;
         mPowerInteractor = powerInteractor;
         mLogger = logger;
+        mJavaAdapter = javaAdapter;
+        mShadeInteractor = shadeInteractor;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
@@ -276,8 +289,25 @@
         mRemoteInputUriController = remoteInputUriController;
         mRemoteInputControllerLogger = remoteInputControllerLogger;
         mClickNotifier = clickNotifier;
+    }
 
-        dumpManager.registerDumpable(this);
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(mShadeInteractor.isAnyExpanded(),
+                this::onShadeOrQsExpanded);
+    }
+
+    private void onShadeOrQsExpanded(boolean expanded) {
+        if (expanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+            try {
+                mBarService.clearNotificationEffects();
+            } catch (RemoteException e) {
+                // Won't fail unless the world has ended.
+            }
+        }
+        if (!expanded) {
+            onPanelCollapsed();
+        }
     }
 
     /** Add a listener for various remote input events.  Works with NEW pipeline only. */
@@ -535,7 +565,8 @@
     public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {
         if (isRemoteInputActive(entry)) {
             entry.mRemoteEditImeVisible = false;
-            mRemoteInputController.removeRemoteInput(entry, null);
+            mRemoteInputController.removeRemoteInput(entry, null,
+                    /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 09ad55e..23b697e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -37,11 +37,11 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
@@ -96,10 +96,10 @@
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
     private float mActualWidth = -1;
-    private final ViewRefactorFlag mSensitiveRevealAnim =
-            new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM);
-    private final ViewRefactorFlag mShelfRefactor =
-            new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR);
+    private final RefactorFlag mSensitiveRevealAnim =
+            RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
+    private final RefactorFlag mShelfRefactor =
+            RefactorFlag.forView(Flags.NOTIFICATION_SHELF_REFACTOR);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -133,7 +133,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -143,7 +143,7 @@
 
     public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
             NotificationRoundnessManager roundnessManager) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mAmbientState = ambientState;
         mHostLayout = hostLayout;
         mRoundnessManager = roundnessManager;
@@ -964,7 +964,7 @@
 
     @Override
     public void onStateChanged(int newState) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mStatusBarState = newState;
         updateInteractiveness();
     }
@@ -1022,17 +1022,17 @@
     }
 
     public void setController(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mController = notificationShelfController;
     }
 
     public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mCanModifyColorOfNotifications = canModifyColorOfNotifications;
     }
 
     public void setCanInteract(boolean canInteract) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mCanInteract = canInteract;
         updateInteractiveness();
     }
@@ -1050,7 +1050,7 @@
     }
 
     public void requestRoundnessResetFor(ExpandableView child) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         child.requestRoundnessReset(SHELF_SCROLL);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index d5e4902..ffde8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -31,6 +31,8 @@
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.util.DumpUtilsKt;
 
+import com.google.errorprone.annotations.CompileTimeConstant;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Objects;
@@ -69,7 +71,8 @@
      * @param entry the entry for which a remote input is now active.
      * @param token a token identifying the view that is managing the remote input
      */
-    public void addRemoteInput(NotificationEntry entry, Object token) {
+    public void addRemoteInput(NotificationEntry entry, Object token,
+            @CompileTimeConstant String reason) {
         Objects.requireNonNull(entry);
         Objects.requireNonNull(token);
         boolean isActive = isRemoteInputActive(entry);
@@ -77,7 +80,9 @@
                 entry /* contains */, null /* remove */, token /* removeToken */);
         mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,
                 isActive /* isRemoteInputAlreadyActive */,
-                found /* isRemoteInputFound */);
+                found /* isRemoteInputFound */,
+                reason /* reason */,
+                entry.getNotificationStyle()/* notificationStyle */);
         if (!found) {
             mOpen.add(new Pair<>(new WeakReference<>(entry), token));
         }
@@ -96,7 +101,8 @@
      *              the entry is only removed if the token matches the last added token for this
      *              entry. If null, the entry is removed regardless.
      */
-    public void removeRemoteInput(NotificationEntry entry, Object token) {
+    public void removeRemoteInput(NotificationEntry entry, Object token,
+            @CompileTimeConstant String reason) {
         Objects.requireNonNull(entry);
         if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
             mLogger.logRemoveRemoteInput(
@@ -104,19 +110,35 @@
                     true /* remoteEditImeVisible */,
                     true /* remoteEditImeAnimatingAway */,
                     isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */,
-                    isRemoteInputActive() /* isRemoteInputActive */);
+                    isRemoteInputActive() /* isRemoteInputActive */,
+                    reason /* reason */,
+                    entry.getNotificationStyle()/* notificationStyle */);
             return;
         }
+
         // If the view is being removed, this may be called even though we're not active
         boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
+        boolean remoteInputActive = isRemoteInputActive();
         mLogger.logRemoveRemoteInput(
                 entry.getKey() /* entryKey */,
                 entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
                 entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
                 remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
-                isRemoteInputActive()/* isRemoteInputActive */);
+                remoteInputActive/* isRemoteInputActive */,
+                reason/* reason */,
+                entry.getNotificationStyle()/* notificationStyle */);
 
-        if (!remoteInputActiveForEntry) return;
+        if (!remoteInputActiveForEntry) {
+            if (mLastAppliedRemoteInputActive != null
+                    && mLastAppliedRemoteInputActive
+                    && !remoteInputActive) {
+                mLogger.logRemoteInputApplySkipped(
+                        entry.getKey() /* entryKey */,
+                        reason/* reason */,
+                        entry.getNotificationStyle()/* notificationStyle */);
+            }
+            return;
+        }
 
         pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 37a4ef1..537f8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,15 +42,16 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.systemui.DejankUtils;
-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.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import dagger.Lazy;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,8 +65,7 @@
 @SysUISingleton
 public class StatusBarStateControllerImpl implements
         SysuiStatusBarStateController,
-        CallbackController<StateListener>,
-        Dumpable {
+        CallbackController<StateListener> {
     private static final String TAG = "SbStateController";
     private static final boolean DEBUG_IMMERSIVE_APPS =
             SystemProperties.getBoolean("persist.debug.immersive_apps", false);
@@ -95,6 +95,8 @@
     private final ArrayList<RankedListener> mListeners = new ArrayList<>();
     private final UiEventLogger mUiEventLogger;
     private final InteractionJankMonitor mInteractionJankMonitor;
+    private final JavaAdapter mJavaAdapter;
+    private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private int mState;
     private int mLastState;
     private int mUpcomingState;
@@ -156,18 +158,22 @@
     @Inject
     public StatusBarStateControllerImpl(
             UiEventLogger uiEventLogger,
-            DumpManager dumpManager,
             InteractionJankMonitor interactionJankMonitor,
-            ShadeExpansionStateManager shadeExpansionStateManager
-    ) {
+            JavaAdapter javaAdapter,
+            Lazy<ShadeInteractor> shadeInteractorLazy) {
         mUiEventLogger = uiEventLogger;
         mInteractionJankMonitor = interactionJankMonitor;
+        mJavaAdapter = javaAdapter;
+        mShadeInteractorLazy = shadeInteractorLazy;
         for (int i = 0; i < HISTORY_SIZE; i++) {
             mHistoricalRecords[i] = new HistoricalState();
         }
-        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+    }
 
-        dumpManager.registerDumpable(this);
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
+                this::onShadeOrQsExpanded);
     }
 
     @Override
@@ -345,7 +351,7 @@
         }
     }
 
-    private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+    private void onShadeOrQsExpanded(Boolean isExpanded) {
         if (mIsExpanded != isExpanded) {
             mIsExpanded = isExpanded;
             String tag = getClass().getSimpleName() + "#setIsExpanded";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 1196211..595ab70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -21,6 +21,21 @@
 public interface StatusIconDisplayable extends DarkReceiver {
     String getSlot();
     void setStaticDrawableColor(int color);
+
+    /**
+     * For a layer drawable, or one that has a background, {@code tintColor} should be used as the
+     * background tint for the container, while {@code contrastColor} can be used as the foreground
+     * drawable's tint so that it is visible on the background. Essentially, tintColor should apply
+     * to the portion of the icon that borders the underlying window content (status bar's
+     * background), and the contrastColor only need be used to distinguish from the tintColor.
+     *
+     * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern
+     * callers can just call this method and still get the default behavior.
+     */
+    default void setStaticDrawableColor(int tintColor, int contrastColor) {
+        setStaticDrawableColor(tintColor);
+    }
+
     void setDecorColor(int color);
 
     /** Sets the visible state that this displayable should be. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index aa32d5c..8104755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.view.View;
 
+import com.android.systemui.CoreStartable;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -29,7 +30,7 @@
 /**
  * Sends updates to {@link StateListener}s about changes to the status bar state and dozing state
  */
-public interface SysuiStatusBarStateController extends StatusBarStateController {
+public interface SysuiStatusBarStateController extends StatusBarStateController, CoreStartable {
 
     // TODO: b/115739177 (remove this explicit ordering if we can)
     @Retention(SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7f5829d..a957095 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,52 +16,41 @@
 
 package com.android.systemui.statusbar.dagger;
 
-import android.app.WallpaperManager;
 import android.content.Context;
-import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
 import android.util.Log;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.AnimationFeatureFlags;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
-import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.MediaArtworkProcessor;
 import com.android.systemui.statusbar.NotificationClickNotifier;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -70,13 +59,13 @@
 import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 /**
  * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
@@ -86,72 +75,30 @@
  */
 @Module(includes = {StatusBarNotificationPresenterModule.class})
 public interface CentralSurfacesDependenciesModule {
+
     /** */
-    @SysUISingleton
-    @Provides
-    static NotificationRemoteInputManager provideNotificationRemoteInputManager(
-            Context context,
-            NotifPipelineFlags notifPipelineFlags,
-            NotificationLockscreenUserManager lockscreenUserManager,
-            SmartReplyController smartReplyController,
-            NotificationVisibilityProvider visibilityProvider,
-            PowerInteractor powerInteractor,
-            StatusBarStateController statusBarStateController,
-            RemoteInputUriController remoteInputUriController,
-            RemoteInputControllerLogger remoteInputControllerLogger,
-            NotificationClickNotifier clickNotifier,
-            ActionClickLogger actionClickLogger,
-            DumpManager dumpManager) {
-        return new NotificationRemoteInputManager(
-                context,
-                notifPipelineFlags,
-                lockscreenUserManager,
-                smartReplyController,
-                visibilityProvider,
-                powerInteractor,
-                statusBarStateController,
-                remoteInputUriController,
-                remoteInputControllerLogger,
-                clickNotifier,
-                actionClickLogger,
-                dumpManager);
-    }
+    @Binds
+    @IntoMap
+    @ClassKey(NotificationRemoteInputManager.class)
+    CoreStartable bindsStartNotificationRemoteInputManager(NotificationRemoteInputManager nrim);
 
     /** */
     @SysUISingleton
     @Provides
     static NotificationMediaManager provideNotificationMediaManager(
             Context context,
-            Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationVisibilityProvider visibilityProvider,
-            MediaArtworkProcessor mediaArtworkProcessor,
-            KeyguardBypassController keyguardBypassController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
-            @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
-            StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor,
-            KeyguardStateController keyguardStateController,
-            DumpManager dumpManager,
-            WallpaperManager wallpaperManager,
-            DisplayManager displayManager) {
+            DumpManager dumpManager) {
         return new NotificationMediaManager(
                 context,
-                notificationShadeWindowController,
                 visibilityProvider,
-                mediaArtworkProcessor,
-                keyguardBypassController,
                 notifPipeline,
                 notifCollection,
-                mainExecutor,
                 mediaDataManager,
-                statusBarStateController,
-                colorExtractor,
-                keyguardStateController,
-                dumpManager,
-                wallpaperManager,
-                displayManager);
+                dumpManager);
     }
 
     /** */
@@ -190,20 +137,23 @@
         return new CommandQueue(context, displayTracker, registry, dumpHandler, powerInteractor);
     }
 
-    /**
-     */
+    /** */
     @Binds
     ManagedProfileController provideManagedProfileController(
             ManagedProfileControllerImpl controllerImpl);
 
-    /**
-     */
+    /** */
     @Binds
     SysuiStatusBarStateController providesSysuiStatusBarStateController(
             StatusBarStateControllerImpl statusBarStateControllerImpl);
 
-    /**
-     */
+    /** */
+    @Binds
+    @IntoMap
+    @ClassKey(SysuiStatusBarStateController.class)
+    CoreStartable bindsStartStatusBarStateController(StatusBarStateControllerImpl sbsc);
+
+    /** */
     @Binds
     StatusBarIconController provideStatusBarIconController(
             StatusBarIconControllerImpl controllerImpl);
@@ -238,16 +188,14 @@
     ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
             ShadeCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
 
-    /**
-     */
+    /** */
     @Provides
     @SysUISingleton
     static ActivityLaunchAnimator provideActivityLaunchAnimator() {
         return new ActivityLaunchAnimator();
     }
 
-    /**
-     */
+    /** */
     @Provides
     @SysUISingleton
     static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
@@ -279,8 +227,7 @@
         return new DialogLaunchAnimator(callback, interactionJankMonitor, animationFeatureFlags);
     }
 
-    /**
-     */
+    /** */
     @Provides
     @SysUISingleton
     static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index e2de37f..22912df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,18 +17,13 @@
 package com.android.systemui.statusbar.dagger
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.core.StatusBarInitializer
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
+import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
 
 /**
  * A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -38,24 +33,9 @@
  *   ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
  *   [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
  */
-@Module
+@Module(includes = [StatusBarDataLayerModule::class])
 abstract class StatusBarModule {
     @Binds
-    abstract fun bindStatusBarModeRepository(
-        impl: StatusBarModeRepositoryImpl
-    ): StatusBarModeRepository
-
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBarModeRepositoryImpl::class)
-    abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable
-
-    @Binds
-    abstract fun bindKeyguardStatusBarRepository(
-        impl: KeyguardStatusBarRepositoryImpl
-    ): KeyguardStatusBarRepository
-
-    @Binds
     @IntoMap
     @ClassKey(OngoingCallController::class)
     abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@@ -64,10 +44,4 @@
     @IntoMap
     @ClassKey(LightBarController::class)
     abstract fun bindLightBarController(impl: LightBarController): CoreStartable
-
-    @Binds
-    @IntoSet
-    abstract fun statusBarInitializedListener(
-        statusBarModeRepository: StatusBarModeRepository,
-    ): StatusBarInitializer.OnStatusBarViewInitializedListener
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
new file mode 100644
index 0000000..29d53fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.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.statusbar.data
+
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
+import dagger.Module
+
+@Module(
+    includes =
+        [
+            KeyguardStatusBarRepositoryModule::class,
+            StatusBarModeRepositoryModule::class,
+            StatusBarPhoneDataLayerModule::class
+        ]
+)
+object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
index 8136de9..d1594ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.user.data.repository.UserSwitcherRepository
+import dagger.Binds
+import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -78,3 +80,8 @@
             isEnabled && isKeyguardEnabled
         }
 }
+
+@Module
+interface KeyguardStatusBarRepositoryModule {
+    @Binds fun bindImpl(impl: KeyguardStatusBarRepositoryImpl): KeyguardStatusBarRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
new file mode 100644
index 0000000..2c706a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationListener
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Exposes state pertaining to settings tracked over the [NotificationListener] boundary. */
+@SysUISingleton
+class NotificationListenerSettingsRepository @Inject constructor() {
+    /** Should icons for silent notifications be shown in the status bar? */
+    val showSilentStatusIcons = MutableStateFlow(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
index 2b05994..47994d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
 import com.android.systemui.statusbar.data.model.StatusBarMode
 import com.android.systemui.statusbar.phone.BoundsPair
@@ -38,6 +38,11 @@
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -56,7 +61,7 @@
  * Note: These status bar modes are status bar *window* states that are sent to us from
  * WindowManager, not determined internally.
  */
-interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
+interface StatusBarModeRepository {
     /**
      * True if the status bar window is showing transiently and will disappear soon, and false
      * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -112,7 +117,7 @@
     private val commandQueue: CommandQueue,
     private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
     ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModeRepository, CoreStartable {
+) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener {
 
     private val commandQueueCallback =
         object : CommandQueue.Callbacks {
@@ -334,3 +339,17 @@
         val statusBarBounds: BoundsPair,
     )
 }
+
+@Module
+interface StatusBarModeRepositoryModule {
+    @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository
+
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBarModeRepositoryImpl::class)
+    fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
+
+    @Binds
+    @IntoSet
+    fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
new file mode 100644
index 0000000..1248b1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import javax.inject.Inject
+
+class SilentNotificationStatusIconsVisibilityInteractor
+@Inject
+constructor(private val repository: NotificationListenerSettingsRepository) {
+    /** Set whether icons for silent notifications be hidden in the status bar. */
+    fun setHideSilentStatusIcons(hideIcons: Boolean) {
+        repository.showSilentStatusIcons.value = !hideIcons
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index bde298d..ea1d782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -36,6 +36,11 @@
     val showAnimation: Boolean
     val viewCreator: ViewCreator
     var contentDescription: String?
+    /**
+     * When true, an accessibility event with [contentDescription] is announced when the view
+     * becomes visible.
+     */
+    val shouldAnnounceAccessibilityEvent: Boolean
 
     // Update this event with values from another event.
     fun updateFromEvent(other: StatusEvent?) {
@@ -76,6 +81,7 @@
     override var forceVisible = false
     override val showAnimation = true
     override var contentDescription: String? = ""
+    override val shouldAnnounceAccessibilityEvent: Boolean = false
 
     override val viewCreator: ViewCreator = { context ->
         BatteryStatusChip(context).apply {
@@ -95,6 +101,7 @@
     override var forceVisible = false
     override val showAnimation = true
     override var contentDescription: String? = ""
+    override val shouldAnnounceAccessibilityEvent: Boolean = true
 
     override val viewCreator: ViewCreator = { context ->
         ConnectedDisplayChip(context)
@@ -110,6 +117,7 @@
     override var contentDescription: String? = null
     override val priority = 100
     override var forceVisible = true
+    override val shouldAnnounceAccessibilityEvent: Boolean = false
     var privacyItems: List<PrivacyItem> = listOf()
     private var privacyChip: OngoingPrivacyChip? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index dccc23f..73c0bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -273,6 +273,11 @@
         })
     }
 
+    /** Announces [contentDescriptions] for accessibility. */
+    fun announceForAccessibility(contentDescriptions: String) {
+        currentAnimatedView?.view?.announceForAccessibility(contentDescriptions)
+    }
+
     private fun updateDimens(contentArea: Rect) {
         val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
         lp.height = contentArea.height()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 7d866df..8ee1ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -100,9 +100,12 @@
     }
 
     private fun startConnectedDisplayCollection() {
+        val connectedDisplayEvent = ConnectedDisplayEvent().apply {
+            contentDescription = context.getString(R.string.connected_display_icon_desc)
+        }
         connectedDisplayCollectionJob =
                 onDisplayConnectedFlow
-                        .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) }
+                        .onEach { scheduler.onStatusEvent(connectedDisplayEvent) }
                         .launchIn(appScope)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index a3bc002..f0e60dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -254,11 +254,18 @@
         currentlyRunningAnimationJob =
             coroutineScope.launch {
                 runChipAppearAnimation()
+                announceForAccessibilityIfNeeded(event)
                 delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH)
                 runChipDisappearAnimation()
             }
     }
 
+    private fun announceForAccessibilityIfNeeded(event: StatusEvent) {
+        val description = event.contentDescription ?: return
+        if (!event.shouldAnnounceAccessibilityEvent)  return
+        chipAnimationController.announceForAccessibility(description)
+    }
+
     /**
      * 1. Define a total budget for the chip animation (1500ms)
      * 2. Send out callbacks to listeners so that they can generate animations locally
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
deleted file mode 100644
index 732c115..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ /dev/null
@@ -1,131 +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.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-
-import androidx.palette.graphics.Palette;
-
-import java.util.List;
-
-/**
- * A gutted class that now contains only a color extraction utility used by the
- * MediaArtworkProcessor, which has otherwise supplanted this.
- *
- * TODO(b/182926117): move this into MediaArtworkProcessor.kt
- */
-public class MediaNotificationProcessor {
-
-    /**
-     * The population fraction to select a white or black color as the background over a color.
-     */
-    private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
-    private static final float BLACK_MAX_LIGHTNESS = 0.08f;
-    private static final float WHITE_MIN_LIGHTNESS = 0.90f;
-    private static final int RESIZE_BITMAP_AREA = 150 * 150;
-
-    private MediaNotificationProcessor() {
-    }
-
-    /**
-     * Finds an appropriate background swatch from media artwork.
-     *
-     * @param artwork Media artwork
-     * @return Swatch that should be used as the background of the media notification.
-     */
-    public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) {
-        return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate());
-    }
-
-    /**
-     * Finds an appropriate background swatch from the palette of media artwork.
-     *
-     * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
-     * @return Swatch that should be used as the background of the media notification.
-     */
-    public static Palette.Swatch findBackgroundSwatch(Palette palette) {
-        // by default we use the dominant palette
-        Palette.Swatch dominantSwatch = palette.getDominantSwatch();
-        if (dominantSwatch == null) {
-            return new Palette.Swatch(Color.WHITE, 100);
-        }
-
-        if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
-            return dominantSwatch;
-        }
-        // Oh well, we selected black or white. Lets look at the second color!
-        List<Palette.Swatch> swatches = palette.getSwatches();
-        float highestNonWhitePopulation = -1;
-        Palette.Swatch second = null;
-        for (Palette.Swatch swatch : swatches) {
-            if (swatch != dominantSwatch
-                    && swatch.getPopulation() > highestNonWhitePopulation
-                    && !isWhiteOrBlack(swatch.getHsl())) {
-                second = swatch;
-                highestNonWhitePopulation = swatch.getPopulation();
-            }
-        }
-        if (second == null) {
-            return dominantSwatch;
-        }
-        if (dominantSwatch.getPopulation() / highestNonWhitePopulation
-                > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
-            // The dominant swatch is very dominant, lets take it!
-            // We're not filtering on white or black
-            return dominantSwatch;
-        } else {
-            return second;
-        }
-    }
-
-    /**
-     * Generate a palette builder for media artwork.
-     *
-     * For producing a smooth background transition, the palette is extracted from only the left
-     * side of the artwork.
-     *
-     * @param artwork Media artwork
-     * @return Builder that generates the {@link Palette} for the media artwork.
-     */
-    public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
-        // for the background we only take the left side of the image to ensure
-        // a smooth transition
-        return Palette.from(artwork)
-                .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight())
-                .clearFilters() // we want all colors, red / white / black ones too!
-                .resizeBitmapArea(RESIZE_BITMAP_AREA);
-    }
-
-    private static boolean isWhiteOrBlack(float[] hsl) {
-        return isBlack(hsl) || isWhite(hsl);
-    }
-
-    /**
-     * @return true if the color represents a color which is close to black.
-     */
-    private static boolean isBlack(float[] hslColor) {
-        return hslColor[2] <= BLACK_MAX_LIGHTNESS;
-    }
-
-    /**
-     * @return true if the color represents a color which is close to white.
-     */
-    private static boolean isWhite(float[] hslColor) {
-        return hslColor[2] >= WHITE_MIN_LIGHTNESS;
-    }
-}
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 756151b..96279e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -21,7 +21,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.LaunchAnimator
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -33,7 +33,7 @@
 
 /** A provider of [NotificationLaunchAnimatorController]. */
 class NotificationLaunchAnimatorControllerProvider(
-    private val notificationExpansionRepository: NotificationExpansionRepository,
+    private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
     private val notificationListContainer: NotificationListContainer,
     private val headsUpManager: HeadsUpManager,
     private val jankMonitor: InteractionJankMonitor
@@ -44,7 +44,7 @@
         onFinishAnimationCallback: Runnable? = null
     ): NotificationLaunchAnimatorController {
         return NotificationLaunchAnimatorController(
-            notificationExpansionRepository,
+            notificationLaunchAnimationInteractor,
             notificationListContainer,
             headsUpManager,
             notification,
@@ -60,7 +60,7 @@
  * notification expanding into an opening window.
  */
 class NotificationLaunchAnimatorController(
-    private val notificationExpansionRepository: NotificationExpansionRepository,
+    private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
     private val notificationListContainer: NotificationListContainer,
     private val headsUpManager: HeadsUpManager,
     private val notification: ExpandableNotificationRow,
@@ -143,7 +143,7 @@
         if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
             Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
         }
-        notificationExpansionRepository.setIsExpandAnimationRunning(willAnimate)
+        notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
         notificationEntry.isExpandAnimationRunning = willAnimate
 
         if (!willAnimate) {
@@ -180,7 +180,7 @@
 
         // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
         // here?
-        notificationExpansionRepository.setIsExpandAnimationRunning(false)
+        notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
         notificationEntry.isExpandAnimationRunning = false
         removeHun(animate = true)
         onFinishAnimationCallback?.run()
@@ -200,7 +200,7 @@
         jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
 
         notification.isExpandAnimationRunning = false
-        notificationExpansionRepository.setIsExpandAnimationRunning(false)
+        notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
         notificationEntry.isExpandAnimationRunning = false
         notificationListContainer.setExpandingNotification(null)
         applyParams(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 57d20246..f98f39e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.util.Log;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -33,6 +34,7 @@
  * An animator to animate properties
  */
 public class PropertyAnimator {
+    private static final String TAG = "PropertyAnimator";
 
     /**
      * Set a property on a view, updating its value, even if it's already animating.
@@ -114,18 +116,23 @@
                 || previousAnimator.getAnimatedFraction() == 0)) {
             animator.setStartDelay(properties.delay);
         }
-        if (listener != null) {
-            animator.addListener(listener);
-        }
         // remove the tag when the animation is finished
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                view.setTag(animatorTag, null);
-                view.setTag(animationStartTag, null);
-                view.setTag(animationEndTag, null);
+                Animator existing = (Animator) view.getTag(animatorTag);
+                if (existing == animation) {
+                    view.setTag(animatorTag, null);
+                    view.setTag(animationStartTag, null);
+                    view.setTag(animationEndTag, null);
+                } else {
+                    Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up.");
+                }
             }
         });
+        if (listener != null) {
+            animator.addListener(listener);
+        }
         ViewState.startAnimator(animator, listener);
         view.setTag(animatorTag, animator);
         view.setTag(animationStartTag, currentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 39b999c..23f87ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -32,17 +32,24 @@
     fun logAddRemoteInput(
         entryKey: String,
         isRemoteInputAlreadyActive: Boolean,
-        isRemoteInputFound: Boolean
+        isRemoteInputFound: Boolean,
+        reason: String,
+        notificationStyle: String
     ) =
         logBuffer.log(
             TAG,
             DEBUG,
             {
                 str1 = entryKey
+                str2 = reason
+                str3 = notificationStyle
                 bool1 = isRemoteInputAlreadyActive
                 bool2 = isRemoteInputFound
             },
-            { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" }
+            {
+                "addRemoteInput reason:$str2 entry: $str1, style:$str3" +
+                    ", isAlreadyActive: $bool1, isFound:$bool2"
+            }
         )
 
     /** logs removeRemoteInput invocation of [RemoteInputController] */
@@ -52,25 +59,45 @@
         remoteEditImeVisible: Boolean,
         remoteEditImeAnimatingAway: Boolean,
         isRemoteInputActiveForEntry: Boolean,
-        isRemoteInputActive: Boolean
+        isRemoteInputActive: Boolean,
+        reason: String,
+        notificationStyle: String
     ) =
         logBuffer.log(
             TAG,
             DEBUG,
             {
                 str1 = entryKey
+                str2 = reason
+                str3 = notificationStyle
                 bool1 = remoteEditImeVisible
                 bool2 = remoteEditImeAnimatingAway
                 bool3 = isRemoteInputActiveForEntry
                 bool4 = isRemoteInputActive
             },
             {
-                "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+                "removeRemoteInput reason: $str2 entry: $str1" +
+                    ", style: $str3, remoteEditImeVisible: $bool1" +
                     ", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" +
                     ", isRemoteInputActive: $bool4"
             }
         )
 
+    fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = entryKey
+                str2 = reason
+                str3 = notificationStyle
+            },
+            {
+                "removeRemoteInput[apply is skipped] reason: $str2" +
+                    "for entry: $str1, style: $str3 "
+            }
+        )
+
     private companion object {
         private const val TAG = "RemoteInputControllerLog"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 328a741..3e9c6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,10 +3,10 @@
 import android.util.FloatProperty
 import android.view.View
 import androidx.annotation.FloatRange
-import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.flags.RefactorFlag
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import kotlin.math.abs
@@ -323,7 +323,7 @@
     internal var maxRadius = maxRadius
         private set
 
-    internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS)
+    internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags)
 
     /** Animatable for top roundness */
     private val topAnimatable = topAnimatable(roundable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index e763797..7b3a93a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -225,7 +225,7 @@
 
     /** @see NotifPipeline#getEntry(String) () */
     @Nullable
-    NotificationEntry getEntry(@NonNull String key) {
+    public NotificationEntry getEntry(@NonNull String key) {
         return mNotificationSet.get(key);
     }
 
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 affd2d1..cfe9fbe 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
@@ -748,7 +748,11 @@
         return row != null && row.getGuts() != null && row.getGuts().isExposed();
     }
 
-    public boolean isChildInGroup() {
+    /**
+     * @return Whether the notification row is a child of a group notification view; false if the
+     * row is null
+     */
+    public boolean rowIsChildInGroup() {
         return row != null && row.isChildInGroup();
     }
 
@@ -979,6 +983,19 @@
         return mExpandAnimationRunning;
     }
 
+    /**
+     * @return NotificationStyle
+     */
+    public String getNotificationStyle() {
+        if (isSummaryWithChildren()) {
+            return "summary";
+        }
+
+        final Class<? extends Notification.Style> style =
+                getSbn().getNotification().getNotificationStyle();
+        return style == null ? "nostyle" : style.getSimpleName();
+    }
+
     /** Information about a suggestion that is being edited. */
     public static class EditedSuggestionInfo {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2d83970..0c69a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -37,8 +37,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.headsUpEvents
 import com.android.systemui.util.asIndenting
@@ -85,7 +85,7 @@
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     private val secureSettings: SecureSettings,
-    private val notificationListInteractor: NotificationListInteractor,
+    private val seenNotificationsInteractor: SeenNotificationsInteractor,
     private val statusBarStateController: StatusBarStateController,
 ) : Coordinator, Dumpable {
 
@@ -351,7 +351,7 @@
 
             override fun onCleanup() {
                 logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
-                notificationListInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
+                seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
                 hasFilteredAnyNotifs = false
             }
         }
@@ -389,7 +389,7 @@
         with(pw.asIndenting()) {
             println(
                 "notificationListInteractor.hasFilteredOutSeenNotifications.value=" +
-                    notificationListInteractor.hasFilteredOutSeenNotifications.value
+                    seenNotificationsInteractor.hasFilteredOutSeenNotifications.value
             )
             println("unseen notifications:")
             indentIfPossible {
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 9ba1f7a..380cdad 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
@@ -30,6 +30,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.user.domain.interactor.SelectedUserInteractor
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -52,7 +53,8 @@
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val statusBarStateController: StatusBarStateController,
-    private val keyguardStateController: KeyguardStateController
+    private val keyguardStateController: KeyguardStateController,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) : Invalidator("SensitiveContentInvalidator"),
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
@@ -67,10 +69,10 @@
     override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
 
     override fun onBeforeRenderList(entries: List<ListEntry>) {
-        if (keyguardStateController.isKeyguardGoingAway() ||
-                statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+        if (keyguardStateController.isKeyguardGoingAway ||
+                statusBarStateController.state == StatusBarState.KEYGUARD &&
                 keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
-                        KeyguardUpdateMonitor.getCurrentUser())) {
+                        selectedUserInteractor.getSelectedUserId())) {
             // don't update yet if:
             // - the keyguard is currently going away
             // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
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 62a0d13..e0c4bfa 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
@@ -22,19 +22,24 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
 /**
- * A small coordinator which updates the notif stack (the view layer which holds notifications)
- * with high-level data after the stack is populated with the final entries.
+ * A small coordinator which updates the notif stack (the view layer which holds notifications) with
+ * high-level data after the stack is populated with the final entries.
  */
 @CoordinatorScope
-class StackCoordinator @Inject internal constructor(
+class StackCoordinator
+@Inject
+internal constructor(
     private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
-    private val notificationIconAreaController: NotificationIconAreaController
+    private val notificationIconAreaController: NotificationIconAreaController,
+    private val renderListInteractor: RenderNotificationListInteractor,
 ) : Coordinator {
 
     override fun attach(pipeline: NotifPipeline) {
@@ -45,7 +50,11 @@
     fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
         traceSection("StackCoordinator.onAfterRenderList") {
             controller.setNotifStats(calculateNotifStats(entries))
-            notificationIconAreaController.updateNotificationIcons(entries)
+            if (NotificationIconContainerRefactor.isEnabled) {
+                renderListInteractor.setRenderedList(entries)
+            } else {
+                notificationIconAreaController.updateNotificationIcons(entries)
+            }
         }
 
     private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -75,4 +84,4 @@
             hasClearableSilentNotifs = hasClearableSilentNotifs
         )
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 9d56a8e..362786e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -115,6 +115,7 @@
      *
      * @deprecated Use {@link #onRankingApplied()} instead.
      */
+    @Deprecated
     default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
     }
 
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 8561869..fa366c6 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
@@ -54,7 +54,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -204,12 +204,12 @@
     @Provides
     @SysUISingleton
     static NotificationLaunchAnimatorControllerProvider provideNotifLaunchAnimControllerProvider(
-            NotificationExpansionRepository notificationExpansionRepository,
+            NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
             NotificationListContainer notificationListContainer,
             HeadsUpManager headsUpManager,
             InteractionJankMonitor jankMonitor) {
         return new NotificationLaunchAnimatorControllerProvider(
-                notificationExpansionRepository,
+                notificationLaunchAnimationInteractor,
                 notificationListContainer,
                 headsUpManager,
                 jankMonitor);
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
new file mode 100644
index 0000000..8064f04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Repository of "active" notifications in the notification list.
+ *
+ * This repository serves as the boundary between the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] and the modern
+ * notifications presentation codebase.
+ */
+@SysUISingleton
+class ActiveNotificationListRepository @Inject constructor() {
+    /**
+     * Notifications actively presented to the user in the notification stack.
+     *
+     * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+     */
+    val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
+
+    /** Are any already-seen notifications currently filtered out of the active list? */
+    val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
new file mode 100644
index 0000000..afed6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.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.data.repository
+
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** View-states pertaining to heads-up notification icons. */
+@SysUISingleton
+class HeadsUpNotificationIconViewStateRepository @Inject constructor() {
+    /** Notification key for a notification icon to show isolated, or `null` if none. */
+    val isolatedNotification = MutableStateFlow<String?>(null)
+    /** Area to display the isolated notification, or `null` if none. */
+    val isolatedIconLocation = MutableStateFlow<Rect?>(null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
deleted file mode 100644
index 6f0a97a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.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.statusbar.notification.data.repository
-
-import android.util.Log
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-private const val TAG = "NotificationExpansionRepository"
-
-/** A repository tracking the status of notification expansion animations. */
-@SysUISingleton
-class NotificationExpansionRepository @Inject constructor() {
-    private val _isExpandAnimationRunning = MutableStateFlow(false)
-
-    /**
-     * Emits true if an animation that expands a notification object into an opening window is
-     * running and false otherwise.
-     *
-     * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
-     */
-    val isExpandAnimationRunning: Flow<Boolean> = _isExpandAnimationRunning.asStateFlow()
-
-    /** Sets whether the notification expansion animation is currently running. */
-    fun setIsExpandAnimationRunning(running: Boolean) {
-        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
-            Log.d(TAG, "setIsExpandAnimationRunning(running=$running)")
-        }
-        _isExpandAnimationRunning.value = running
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt
new file mode 100644
index 0000000..9b56299
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.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.statusbar.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** A repository tracking the status of notification launch animations. */
+@SysUISingleton
+class NotificationLaunchAnimationRepository @Inject constructor() {
+    val isLaunchAnimationRunning = MutableStateFlow(false)
+}
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
new file mode 100644
index 0000000..bfec60bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.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.domain.interactor
+
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class ActiveNotificationsInteractor
+@Inject
+constructor(
+    repository: ActiveNotificationListRepository,
+) {
+    /** Notifications actively presented to the user in the notification stack, in order. */
+    val notifications: Flow<Collection<ActiveNotificationModel>> =
+        repository.activeNotifications.map { it.values }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
new file mode 100644
index 0000000..17b6e9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.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.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Domain logic pertaining to heads up notification icons. */
+class HeadsUpNotificationIconInteractor
+@Inject
+constructor(
+    private val repository: HeadsUpNotificationIconViewStateRepository,
+) {
+    /** Notification key for a notification icon to show isolated, or `null` if none. */
+    val isolatedIconLocation: Flow<Rect?> = repository.isolatedIconLocation
+
+    /** Area to display the isolated notification, or `null` if none. */
+    val isolatedNotification: Flow<String?> = repository.isolatedNotification
+
+    /** Updates the location where isolated notification icons are shown. */
+    fun setIsolatedIconLocation(rect: Rect?) {
+        repository.isolatedIconLocation.value = rect
+    }
+
+    /** Updates which notification will have its icon displayed isolated. */
+    fun setIsolatedIconNotificationKey(key: String?) {
+        repository.isolatedNotification.value = key
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
index 8f7e269..8079ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
@@ -20,15 +20,14 @@
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import javax.inject.Inject
 
-/** Interactor for notifications in general. */
+/** Interactor for notification alerting. */
 @SysUISingleton
-class NotificationsInteractor
+class NotificationAlertsInteractor
 @Inject
 constructor(
     private val disableFlagsRepository: DisableFlagsRepository,
 ) {
     /** Returns true if notification alerts are allowed. */
-    fun areNotificationAlertsEnabled(): Boolean {
-        return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
-    }
+    fun areNotificationAlertsEnabled(): Boolean =
+        disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
new file mode 100644
index 0000000..22ce4f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.util.Log
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** A repository tracking the status of notification expansion animations. */
+@SysUISingleton
+class NotificationLaunchAnimationInteractor
+@Inject
+constructor(private val repository: NotificationLaunchAnimationRepository) {
+
+    /**
+     * Emits true if an animation that expands a notification object into an opening window is
+     * running and false otherwise.
+     *
+     * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
+     */
+    val isLaunchAnimationRunning: StateFlow<Boolean>
+        get() = repository.isLaunchAnimationRunning
+
+    /** Sets whether the notification expansion launch animation is currently running. */
+    fun setIsLaunchAnimationRunning(running: Boolean) {
+        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+            Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)")
+        }
+        repository.isLaunchAnimationRunning.value = running
+    }
+
+    companion object {
+        private const val TAG = "NotificationLaunchAnimationInteractor"
+    }
+}
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
new file mode 100644
index 0000000..604ecbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.update
+
+private typealias ModelStore = Map<String, ActiveNotificationModel>
+
+/**
+ * Logic for passing information from the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
+ * layers.
+ */
+class RenderNotificationListInteractor
+@Inject
+constructor(
+    private val repository: ActiveNotificationListRepository,
+    private val sectionStyleProvider: SectionStyleProvider,
+) {
+    /**
+     * Sets the current list of rendered notification entries as displayed in the notification
+     * stack.
+     *
+     * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
+     */
+    fun setRenderedList(entries: List<ListEntry>) {
+        repository.activeNotifications.update { existingModels ->
+            entries.associateBy(
+                keySelector = { it.key },
+                valueTransform = { it.toModel(existingModels) },
+            )
+        }
+    }
+
+    private fun ListEntry.toModel(
+        existingModels: ModelStore,
+    ): ActiveNotificationModel =
+        existingModels.createOrReuse(
+            key = key,
+            groupKey = representativeEntry?.sbn?.groupKey,
+            isAmbient = sectionStyleProvider.isMinimized(this),
+            isRowDismissed = representativeEntry?.isRowDismissed == true,
+            isSilent = sectionStyleProvider.isSilent(this),
+            isLastMessageFromReply = representativeEntry?.isLastMessageFromReply == true,
+            isSuppressedFromStatusBar = representativeEntry?.shouldSuppressStatusBar() == true,
+            isPulsing = representativeEntry?.showingPulsing() == true,
+            aodIcon = representativeEntry?.icons?.aodIcon?.sourceIcon,
+            shelfIcon = representativeEntry?.icons?.shelfIcon?.sourceIcon,
+            statusBarIcon = representativeEntry?.icons?.statusBarIcon?.sourceIcon,
+        )
+
+    private fun ModelStore.createOrReuse(
+        key: String,
+        groupKey: String?,
+        isAmbient: Boolean,
+        isRowDismissed: Boolean,
+        isSilent: Boolean,
+        isLastMessageFromReply: Boolean,
+        isSuppressedFromStatusBar: Boolean,
+        isPulsing: Boolean,
+        aodIcon: Icon?,
+        shelfIcon: Icon?,
+        statusBarIcon: Icon?
+    ): ActiveNotificationModel {
+        return this[key]?.takeIf {
+            it.isCurrent(
+                key = key,
+                groupKey = groupKey,
+                isAmbient = isAmbient,
+                isRowDismissed = isRowDismissed,
+                isSilent = isSilent,
+                isLastMessageFromReply = isLastMessageFromReply,
+                isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+                isPulsing = isPulsing,
+                aodIcon = aodIcon,
+                shelfIcon = shelfIcon,
+                statusBarIcon = statusBarIcon
+            )
+        }
+            ?: ActiveNotificationModel(
+                key = key,
+                groupKey = groupKey,
+                isAmbient = isAmbient,
+                isRowDismissed = isRowDismissed,
+                isSilent = isSilent,
+                isLastMessageFromReply = isLastMessageFromReply,
+                isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+                isPulsing = isPulsing,
+                aodIcon = aodIcon,
+                shelfIcon = shelfIcon,
+                statusBarIcon = statusBarIcon,
+            )
+    }
+
+    private fun ActiveNotificationModel.isCurrent(
+        key: String,
+        groupKey: String?,
+        isAmbient: Boolean,
+        isRowDismissed: Boolean,
+        isSilent: Boolean,
+        isLastMessageFromReply: Boolean,
+        isSuppressedFromStatusBar: Boolean,
+        isPulsing: Boolean,
+        aodIcon: Icon?,
+        shelfIcon: Icon?,
+        statusBarIcon: Icon?
+    ): Boolean {
+        return when {
+            key != this.key -> false
+            groupKey != this.groupKey -> false
+            isAmbient != this.isAmbient -> false
+            isRowDismissed != this.isRowDismissed -> false
+            isSilent != this.isSilent -> false
+            isLastMessageFromReply != this.isLastMessageFromReply -> false
+            isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
+            isPulsing != this.isPulsing -> false
+            aodIcon != this.aodIcon -> false
+            shelfIcon != this.shelfIcon -> false
+            statusBarIcon != this.statusBarIcon -> false
+            else -> true
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 3fd68a8..1f7ab96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -14,25 +14,26 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.domain.interactor
+package com.android.systemui.statusbar.notification.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.StateFlow
 
 /** Interactor for business logic associated with the notification stack. */
 @SysUISingleton
-class NotificationListInteractor
+class SeenNotificationsInteractor
 @Inject
 constructor(
-    private val notificationListRepository: NotificationListRepository,
+    private val notificationListRepository: ActiveNotificationListRepository,
 ) {
     /** Are any already-seen notifications currently filtered out of the shade? */
-    val hasFilteredOutSeenNotifications: StateFlow<Boolean>
-        get() = notificationListRepository.hasFilteredOutSeenNotifications
+    val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
+        notificationListRepository.hasFilteredOutSeenNotifications
 
+    /** Set whether already-seen notifications are currently filtered out of the shade. */
     fun setHasFilteredOutSeenNotifications(value: Boolean) {
-        notificationListRepository.setHasFilteredOutSeenNotifications(value)
+        notificationListRepository.hasFilteredOutSeenNotifications.value = value
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
new file mode 100644
index 0000000..94e70e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the FooterView refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object FooterViewRefactor {
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationsFooterViewRefactor()
+
+    /**
+     * 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/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index e74b3fc..ba91654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -19,6 +19,9 @@
 import static android.graphics.PorterDuff.Mode.SRC_ATOP;
 
 import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -35,6 +38,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.row.FooterViewButton;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -44,6 +48,8 @@
 import java.io.PrintWriter;
 
 public class FooterView extends StackScrollerDecorView {
+    private static final String TAG = "FooterView";
+
     private FooterViewButton mClearAllButton;
     private FooterViewButton mManageButton;
     private boolean mShowHistory;
@@ -57,6 +63,9 @@
     private String mSeenNotifsFilteredText;
     private Drawable mSeenNotifsFilteredIcon;
 
+    private @StringRes int mMessageStringId;
+    private @DrawableRes int mMessageIconId;
+
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -84,6 +93,50 @@
         });
     }
 
+    /** Set the string for a message to be shown instead of the buttons. */
+    public void setMessageString(@StringRes int messageId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+        if (mMessageStringId == messageId) {
+            return; // nothing changed
+        }
+        mMessageStringId = messageId;
+        updateMessageString();
+    }
+
+    private void updateMessageString() {
+        if (mMessageStringId == 0) {
+            return; // not initialized yet
+        }
+        String messageString = getContext().getString(mMessageStringId);
+        mSeenNotifsFooterTextView.setText(messageString);
+    }
+
+
+    /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
+    public void setMessageIcon(@DrawableRes int iconId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+        if (mMessageIconId == iconId) {
+            return; // nothing changed
+        }
+        mMessageIconId = iconId;
+        updateMessageIcon();
+    }
+
+    private void updateMessageIcon() {
+        if (mMessageIconId == 0) {
+            return; // not initialized yet
+        }
+        int unlockIconSize = getResources()
+                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+        @SuppressLint("UseCompatLoadingForDrawables")
+        Drawable messageIcon = getContext().getDrawable(mMessageIconId);
+        if (messageIcon != null) {
+            messageIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+            mSeenNotifsFooterTextView
+                    .setCompoundDrawablesRelative(messageIcon, null, null, null);
+        }
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -148,9 +201,11 @@
             mManageButton.setText(mManageNotificationText);
             mManageButton.setContentDescription(mManageNotificationText);
         }
-        mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
-        mSeenNotifsFooterTextView
-                .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+        if (!FooterViewRefactor.isEnabled()) {
+            mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+            mSeenNotifsFooterTextView
+                    .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+        }
     }
 
     /** Whether the start button shows "History" (true) or "Manage" (false). */
@@ -167,6 +222,11 @@
                 mContext.getString(R.string.accessibility_clear_all));
         updateResources();
         updateContent();
+
+        if (FooterViewRefactor.isEnabled()) {
+            updateMessageString();
+            updateMessageIcon();
+        }
     }
 
     /**
@@ -200,11 +260,13 @@
         mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
         mManageNotificationHistoryText = getContext()
                 .getString(R.string.manage_notifications_history_text);
-        int unlockIconSize = getResources()
-                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
-        mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
-        mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
-        mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+        if (!FooterViewRefactor.isEnabled()) {
+            int unlockIconSize = getResources()
+                    .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+            mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+            mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+            mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
new file mode 100644
index 0000000..6d823437
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [FooterView] to its [view model][FooterViewModel]. */
+object FooterViewBinder {
+    fun bind(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+    ): DisposableHandle {
+        return footer.repeatWhenAttached {
+            // Listen for changes when the view is attached.
+            lifecycleScope.launch {
+                viewModel.message.collect { message ->
+                    footer.setFooterLabelVisible(message.visible)
+                    footer.setMessageString(message.messageId)
+                    footer.setMessageIcon(message.iconId)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
index 0803a01..bc912fb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
@@ -12,15 +12,16 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
-import android.appwidget.AppWidgetProviderInfo
+import android.annotation.DrawableRes
+import android.annotation.StringRes
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
+/** A ViewModel for the string message that can be shown in the footer. */
+data class FooterMessageViewModel(
+    @StringRes val messageId: Int,
+    @DrawableRes val iconId: Int,
+    val visible: Boolean,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
new file mode 100644
index 0000000..ffa5ff0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [FooterView]. */
+@SysUISingleton
+class FooterViewModel
+@Inject
+constructor(seenNotificationsInteractor: SeenNotificationsInteractor) {
+    init {
+        /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode()
+    }
+
+    val message: Flow<FooterMessageViewModel> =
+        seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
+            FooterMessageViewModel(
+                messageId = R.string.unlock_to_see_notif_text,
+                iconId = R.drawable.ic_friction_lock_closed,
+                visible = hasFilteredOutNotifs,
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
new file mode 100644
index 0000000..00d873e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.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.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+
+/** Domain logic related to notification icons. */
+class NotificationIconsInteractor
+@Inject
+constructor(
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+    private val bubbles: Optional<Bubbles>,
+    private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
+) {
+    /** Returns a subset of all active notifications based on the supplied filtration parameters. */
+    fun filteredNotifSet(
+        showAmbient: Boolean = true,
+        showLowPriority: Boolean = true,
+        showDismissed: Boolean = true,
+        showRepliedMessages: Boolean = true,
+        showPulsing: Boolean = true,
+    ): Flow<Set<ActiveNotificationModel>> {
+        return combine(
+            activeNotificationsInteractor.notifications,
+            keyguardViewStateRepository.areNotificationsFullyHidden,
+        ) { notifications, notifsFullyHidden ->
+            notifications
+                .asSequence()
+                .filter { model: ActiveNotificationModel ->
+                    shouldShowNotificationIcon(
+                        model = model,
+                        showAmbient = showAmbient,
+                        showLowPriority = showLowPriority,
+                        showDismissed = showDismissed,
+                        showRepliedMessages = showRepliedMessages,
+                        showPulsing = showPulsing,
+                        notifsFullyHidden = notifsFullyHidden,
+                    )
+                }
+                .toSet()
+        }
+    }
+
+    private fun shouldShowNotificationIcon(
+        model: ActiveNotificationModel,
+        showAmbient: Boolean,
+        showLowPriority: Boolean,
+        showDismissed: Boolean,
+        showRepliedMessages: Boolean,
+        showPulsing: Boolean,
+        notifsFullyHidden: Boolean,
+    ): Boolean {
+        return when {
+            !showAmbient && model.isAmbient -> false
+            !showLowPriority && model.isSilent -> false
+            !showDismissed && model.isRowDismissed -> false
+            !showRepliedMessages && model.isLastMessageFromReply -> false
+            !showAmbient && model.isSuppressedFromStatusBar -> false
+            !showPulsing && model.isPulsing && !notifsFullyHidden -> false
+            bubbles.getOrNull()?.isBubbleExpanded(model.key) == true -> false
+            else -> true
+        }
+    }
+}
+
+/** Domain logic related to notification icons shown on the always-on display. */
+class AlwaysOnDisplayNotificationIconsInteractor
+@Inject
+constructor(
+    deviceEntryInteractor: DeviceEntryInteractor,
+    iconsInteractor: NotificationIconsInteractor,
+) {
+    val aodNotifs: Flow<Set<ActiveNotificationModel>> =
+        deviceEntryInteractor.isBypassEnabled.flatMapLatest { isBypassEnabled ->
+            iconsInteractor.filteredNotifSet(
+                showAmbient = false,
+                showDismissed = false,
+                showRepliedMessages = false,
+                showPulsing = !isBypassEnabled,
+            )
+        }
+}
+
+/** Domain logic related to notification icons shown in the status bar. */
+class StatusBarNotificationIconsInteractor
+@Inject
+constructor(
+    iconsInteractor: NotificationIconsInteractor,
+    settingsRepository: NotificationListenerSettingsRepository,
+) {
+    val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
+        settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
+            iconsInteractor.filteredNotifSet(
+                showAmbient = false,
+                showLowPriority = showSilentIcons,
+                showDismissed = false,
+                showRepliedMessages = false,
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index 805a4db..1992ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -16,52 +16,17 @@
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
 import android.content.Context
-import android.graphics.Color
 import android.graphics.Rect
-import android.os.Bundle
-import android.os.Trace
-import android.view.LayoutInflater
 import android.view.View
-import android.widget.FrameLayout
-import androidx.annotation.ColorInt
-import androidx.annotation.VisibleForTesting
-import androidx.collection.ArrayMap
-import com.android.internal.statusbar.StatusBarIcon
-import com.android.internal.util.ContrastColorUtil
-import com.android.settingslib.Utils
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.demomode.DemoMode
-import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ViewRefactorFlag
-import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationListener
-import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.NotificationShelfController
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.NotificationUtils
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.wm.shell.bubbles.Bubbles
-import java.util.Optional
-import java.util.function.Function
 import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
 
 /**
  * Controller class for [NotificationIconContainer]. This implementation serves as a temporary
@@ -71,515 +36,44 @@
  * can be used directly.
  */
 @SysUISingleton
-class NotificationIconAreaControllerViewBinderWrapperImpl
-@Inject
-constructor(
-    private val context: Context,
-    private val wakeUpCoordinator: NotificationWakeUpCoordinator,
-    private val bypassController: KeyguardBypassController,
-    private val configurationController: ConfigurationController,
-    private val mediaManager: NotificationMediaManager,
-    notificationListener: NotificationListener,
-    private val dozeParameters: DozeParameters,
-    private val sectionStyleProvider: SectionStyleProvider,
-    private val bubblesOptional: Optional<Bubbles>,
-    demoModeController: DemoModeController,
-    darkIconDispatcher: DarkIconDispatcher,
-    private val featureFlags: FeatureFlagsClassic,
-    private val statusBarWindowController: StatusBarWindowController,
-    private val screenOffAnimationController: ScreenOffAnimationController,
-    private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
-    private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
-    private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-) :
-    NotificationIconAreaController,
-    DarkIconDispatcher.DarkReceiver,
-    NotificationWakeUpCoordinator.WakeUpListener,
-    DemoMode {
-
-    private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
-    private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
-    private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
-    private val tintAreas = ArrayList<Rect>()
-
-    private var iconSize = 0
-    private var iconHPadding = 0
-    private var iconTint = Color.WHITE
-    private var notificationEntries = listOf<ListEntry>()
-    private var notificationIconArea: View? = null
-    private var notificationIcons: NotificationIconContainer? = null
-    private var shelfIcons: NotificationIconContainer? = null
-    private var aodIcons: NotificationIconContainer? = null
-    private var aodBindJob: DisposableHandle? = null
-    private var aodIconAppearTranslation = 0
-    private var aodIconTint = 0
-    private var showLowPriority = true
-
-    @VisibleForTesting
-    val settingsListener: NotificationListener.NotificationSettingsListener =
-        object : NotificationListener.NotificationSettingsListener {
-            override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
-                showLowPriority = !hideSilentStatusIcons
-                updateStatusBarIcons()
-            }
-        }
-
-    init {
-        wakeUpCoordinator.addListener(this)
-        demoModeController.addCallback(this)
-        notificationListener.addNotificationSettingsListener(settingsListener)
-        initializeNotificationAreaViews(context)
-        reloadAodColor()
-        darkIconDispatcher.addDarkReceiver(this)
-    }
-
-    @VisibleForTesting
-    fun shouldShowLowPriorityIcons(): Boolean {
-        return showLowPriority
-    }
+class NotificationIconAreaControllerViewBinderWrapperImpl @Inject constructor() :
+    NotificationIconAreaController {
 
     /** Called by the Keyguard*ViewController whose view contains the aod icons. */
-    override fun setupAodIcons(aodIcons: NotificationIconContainer) {
-        val changed = this.aodIcons != null && aodIcons !== this.aodIcons
-        if (changed) {
-            this.aodIcons!!.setAnimationsEnabled(false)
-            this.aodIcons!!.removeAllViews()
-            aodBindJob?.dispose()
-        }
-        this.aodIcons = aodIcons
-        this.aodIcons!!.setOnLockScreen(true)
-        aodBindJob =
-            NotificationIconContainerViewBinder.bind(
-                aodIcons,
-                aodIconsViewModel,
-                configurationController,
-                dozeParameters,
-                featureFlags,
-                screenOffAnimationController,
-            )
-        if (changed) {
-            updateAodNotificationIcons()
-        }
-        updateIconLayoutParams(context)
-    }
+    override fun setupAodIcons(aodIcons: NotificationIconContainer?) = unsupported
 
     override fun setupShelf(notificationShelfController: NotificationShelfController) =
         NotificationShelfViewBinderWrapperControllerImpl.unsupported
 
-    override fun setShelfIcons(icons: NotificationIconContainer) {
-        if (shelfRefactor.expectEnabled()) {
-            NotificationIconContainerViewBinder.bind(
-                icons,
-                shelfIconsViewModel,
-                configurationController,
-                dozeParameters,
-                featureFlags,
-                screenOffAnimationController,
-            )
-            shelfIcons = icons
-        }
-    }
+    override fun setShelfIcons(icons: NotificationIconContainer) = unsupported
 
-    override fun onDensityOrFontScaleChanged(context: Context) {
-        updateIconLayoutParams(context)
-    }
+    override fun onDensityOrFontScaleChanged(context: Context) = unsupported
 
     /** Returns the view that represents the notification area. */
-    override fun getNotificationInnerAreaView(): View? {
-        return notificationIconArea
-    }
-
-    /**
-     * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
-     * color that should be used to tint any icons in the notification area.
-     *
-     * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
-     * @param darkIntensity
-     */
-    override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
-        this.tintAreas.clear()
-        this.tintAreas.addAll(tintAreas)
-        if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
-            this.iconTint = iconTint
-        }
-        applyNotificationIconsTint()
-    }
+    override fun getNotificationInnerAreaView(): View? = unsupported
 
     /** Updates the notifications with the given list of notifications to display. */
-    override fun updateNotificationIcons(entries: List<ListEntry>) {
-        notificationEntries = entries
-        updateNotificationIcons()
-    }
+    override fun updateNotificationIcons(entries: List<ListEntry>) = unsupported
 
-    private fun updateStatusBarIcons() {
-        updateIconsForLayout(
-            { entry: NotificationEntry -> entry.icons.statusBarIcon },
-            notificationIcons,
-            showAmbient = false /* showAmbient */,
-            showLowPriority = showLowPriority,
-            hideDismissed = true /* hideDismissed */,
-            hideRepliedMessages = true /* hideRepliedMessages */,
-            hideCurrentMedia = false /* hideCurrentMedia */,
-            hidePulsing = false /* hidePulsing */
-        )
-    }
+    override fun updateAodNotificationIcons() = unsupported
 
-    override fun updateAodNotificationIcons() {
-        if (aodIcons == null) {
-            return
-        }
-        updateIconsForLayout(
-            { entry: NotificationEntry -> entry.icons.aodIcon },
-            aodIcons,
-            showAmbient = false /* showAmbient */,
-            showLowPriority = true /* showLowPriority */,
-            hideDismissed = true /* hideDismissed */,
-            hideRepliedMessages = true /* hideRepliedMessages */,
-            hideCurrentMedia = true /* hideCurrentMedia */,
-            hidePulsing = bypassController.bypassEnabled /* hidePulsing */
-        )
-    }
+    override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported
 
-    override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
-        notificationIcons!!.showIconIsolated(icon, animated)
-    }
-
-    override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
-        notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
-    }
+    override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) =
+        unsupported
 
     override fun setAnimationsEnabled(enabled: Boolean) = unsupported
 
-    override fun onThemeChanged() {
-        reloadAodColor()
-        updateAodIconColors()
-    }
+    override fun onThemeChanged() = unsupported
 
-    override fun getHeight(): Int {
-        return if (aodIcons == null) 0 else aodIcons!!.height
-    }
-
-    override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
-        updateAodNotificationIcons()
-        updateAodIconColors()
-    }
-
-    override fun demoCommands(): List<String> {
-        val commands = ArrayList<String>()
-        commands.add(DemoMode.COMMAND_NOTIFICATIONS)
-        return commands
-    }
-
-    override fun dispatchDemoCommand(command: String, args: Bundle) {
-        if (notificationIconArea != null) {
-            val visible = args.getString("visible")
-            val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
-            notificationIconArea?.visibility = vis
-        }
-    }
-
-    override fun onDemoModeFinished() {
-        if (notificationIconArea != null) {
-            notificationIconArea?.visibility = View.VISIBLE
-        }
-    }
-
-    private fun inflateIconArea(inflater: LayoutInflater): View {
-        return inflater.inflate(R.layout.notification_icon_area, null)
-    }
-
-    /** Initializes the views that will represent the notification area. */
-    private fun initializeNotificationAreaViews(context: Context) {
-        reloadDimens(context)
-        val layoutInflater = LayoutInflater.from(context)
-        notificationIconArea = inflateIconArea(layoutInflater)
-        notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
-        NotificationIconContainerViewBinder.bind(
-            notificationIcons!!,
-            statusBarIconsViewModel,
-            configurationController,
-            dozeParameters,
-            featureFlags,
-            screenOffAnimationController,
-        )
-    }
-
-    private fun updateIconLayoutParams(context: Context) {
-        reloadDimens(context)
-        val params = generateIconLayoutParams()
-        for (i in 0 until notificationIcons!!.childCount) {
-            val child = notificationIcons!!.getChildAt(i)
-            child.layoutParams = params
-        }
-        if (shelfIcons != null) {
-            for (i in 0 until shelfIcons!!.childCount) {
-                val child = shelfIcons!!.getChildAt(i)
-                child.layoutParams = params
-            }
-        }
-        if (aodIcons != null) {
-            for (i in 0 until aodIcons!!.childCount) {
-                val child = aodIcons!!.getChildAt(i)
-                child.layoutParams = params
-            }
-        }
-    }
-
-    private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
-        return FrameLayout.LayoutParams(
-            iconSize + 2 * iconHPadding,
-            statusBarWindowController.statusBarHeight
-        )
-    }
-
-    private fun reloadDimens(context: Context) {
-        val res = context.resources
-        iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
-        iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
-        aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
-    }
-
-    private fun shouldShowNotificationIcon(
-        entry: NotificationEntry,
-        showAmbient: Boolean,
-        showLowPriority: Boolean,
-        hideDismissed: Boolean,
-        hideRepliedMessages: Boolean,
-        hideCurrentMedia: Boolean,
-        hidePulsing: Boolean
-    ): Boolean {
-        if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
-            return false
-        }
-        if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
-            return false
-        }
-        if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
-            return false
-        }
-        if (entry.isRowDismissed && hideDismissed) {
-            return false
-        }
-        if (hideRepliedMessages && entry.isLastMessageFromReply) {
-            return false
-        }
-        // showAmbient == show in shade but not shelf
-        if (!showAmbient && entry.shouldSuppressStatusBar()) {
-            return false
-        }
-        if (
-            hidePulsing &&
-                entry.showingPulsing() &&
-                (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
-        ) {
-            return false
-        }
-        return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
-            false
-        } else true
-    }
-
-    private fun updateNotificationIcons() {
-        Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
-        updateStatusBarIcons()
-        updateShelfIcons()
-        updateAodNotificationIcons()
-        applyNotificationIconsTint()
-        Trace.endSection()
-    }
-
-    private fun updateShelfIcons() {
-        if (shelfIcons == null) {
-            return
-        }
-        updateIconsForLayout(
-            { entry: NotificationEntry -> entry.icons.shelfIcon },
-            shelfIcons,
-            showAmbient = true,
-            showLowPriority = true,
-            hideDismissed = false,
-            hideRepliedMessages = false,
-            hideCurrentMedia = false,
-            hidePulsing = false
-        )
-    }
-
-    /**
-     * Updates the notification icons for a host layout. This will ensure that the notification host
-     * layout will have the same icons like the ones in here.
-     *
-     * @param function A function to look up an icon view based on an entry
-     * @param hostLayout which layout should be updated
-     * @param showAmbient should ambient notification icons be shown
-     * @param showLowPriority should icons from silent notifications be shown
-     * @param hideDismissed should dismissed icons be hidden
-     * @param hideRepliedMessages should messages that have been replied to be hidden
-     * @param hidePulsing should pulsing notifications be hidden
-     */
-    private fun updateIconsForLayout(
-        function: Function<NotificationEntry, StatusBarIconView?>,
-        hostLayout: NotificationIconContainer?,
-        showAmbient: Boolean,
-        showLowPriority: Boolean,
-        hideDismissed: Boolean,
-        hideRepliedMessages: Boolean,
-        hideCurrentMedia: Boolean,
-        hidePulsing: Boolean,
-    ) {
-        val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
-        // Filter out ambient notifications and notification children.
-        for (i in notificationEntries.indices) {
-            val entry = notificationEntries[i].representativeEntry
-            if (entry != null && entry.row != null) {
-                if (
-                    shouldShowNotificationIcon(
-                        entry,
-                        showAmbient,
-                        showLowPriority,
-                        hideDismissed,
-                        hideRepliedMessages,
-                        hideCurrentMedia,
-                        hidePulsing
-                    )
-                ) {
-                    val iconView = function.apply(entry)
-                    if (iconView != null) {
-                        toShow.add(iconView)
-                    }
-                }
-            }
-        }
-
-        // In case we are changing the suppression of a group, the replacement shouldn't flicker
-        // and it should just be replaced instead. We therefore look for notifications that were
-        // just replaced by the child or vice-versa to suppress this.
-        val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
-        val toRemove = ArrayList<View>()
-        for (i in 0 until hostLayout!!.childCount) {
-            val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
-            if (!toShow.contains(child)) {
-                var iconWasReplaced = false
-                val removedGroupKey = child.notification.groupKey
-                for (j in toShow.indices) {
-                    val candidate = toShow[j]
-                    if (
-                        candidate.sourceIcon.sameAs(child.sourceIcon) &&
-                            candidate.notification.groupKey == removedGroupKey
-                    ) {
-                        if (!iconWasReplaced) {
-                            iconWasReplaced = true
-                        } else {
-                            iconWasReplaced = false
-                            break
-                        }
-                    }
-                }
-                if (iconWasReplaced) {
-                    var statusBarIcons = replacingIcons[removedGroupKey]
-                    if (statusBarIcons == null) {
-                        statusBarIcons = ArrayList()
-                        replacingIcons[removedGroupKey] = statusBarIcons
-                    }
-                    statusBarIcons.add(child.statusBarIcon)
-                }
-                toRemove.add(child)
-            }
-        }
-        // removing all duplicates
-        val duplicates = ArrayList<String?>()
-        for (key in replacingIcons.keys) {
-            val statusBarIcons = replacingIcons[key]!!
-            if (statusBarIcons.size != 1) {
-                duplicates.add(key)
-            }
-        }
-        replacingIcons.removeAll(duplicates)
-        hostLayout.setReplacingIcons(replacingIcons)
-        val toRemoveCount = toRemove.size
-        for (i in 0 until toRemoveCount) {
-            hostLayout.removeView(toRemove[i])
-        }
-        val params = generateIconLayoutParams()
-        for (i in toShow.indices) {
-            val v = toShow[i]
-            // The view might still be transiently added if it was just removed and added again
-            hostLayout.removeTransientView(v)
-            if (v.parent == null) {
-                if (hideDismissed) {
-                    v.setOnDismissListener(updateStatusBarIcons)
-                }
-                hostLayout.addView(v, i, params)
-            }
-        }
-        hostLayout.setChangingViewPositions(true)
-        // Re-sort notification icons
-        val childCount = hostLayout.childCount
-        for (i in 0 until childCount) {
-            val actual = hostLayout.getChildAt(i)
-            val expected = toShow[i]
-            if (actual === expected) {
-                continue
-            }
-            hostLayout.removeView(expected)
-            hostLayout.addView(expected, i)
-        }
-        hostLayout.setChangingViewPositions(false)
-        hostLayout.setReplacingIcons(null)
-    }
-
-    /** Applies [.mIconTint] to the notification icons. */
-    private fun applyNotificationIconsTint() {
-        for (i in 0 until notificationIcons!!.childCount) {
-            val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
-            if (iv.width != 0) {
-                updateTintForIcon(iv, iconTint)
-            } else {
-                iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
-            }
-        }
-        updateAodIconColors()
-    }
-
-    private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
-        val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
-        var color = StatusBarIconView.NO_COLOR
-        val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
-        if (colorize) {
-            color = DarkIconDispatcher.getTint(tintAreas, v, tint)
-        }
-        v.staticDrawableColor = color
-        v.setDecorColor(tint)
-    }
-
-    private fun reloadAodColor() {
-        aodIconTint =
-            Utils.getColorAttrDefaultColor(
-                context,
-                R.attr.wallpaperTextColor,
-                DEFAULT_AOD_ICON_COLOR
-            )
-    }
-
-    private fun updateAodIconColors() {
-        if (aodIcons != null) {
-            for (i in 0 until aodIcons!!.childCount) {
-                val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
-                if (iv.width != 0) {
-                    updateTintForIcon(iv, aodIconTint)
-                } else {
-                    iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
-                }
-            }
-        }
-    }
+    override fun getHeight(): Int = unsupported
 
     companion object {
-        @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
-
         val unsupported: Nothing
             get() =
                 error(
-                    "Code path not supported when NOTIFICATION_ICON_CONTAINER_REFACTOR is disabled"
+                    "Code path not supported when ${NotificationIconContainerRefactor.FLAG_NAME}" +
+                        " is disabled"
                 )
     }
 }
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 0d2f00a..c1f728a 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
@@ -15,172 +15,465 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
-import android.content.res.Resources
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.graphics.Color
+import android.graphics.Rect
 import android.view.View
-import androidx.annotation.DimenRes
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.collection.ArrayMap
+import androidx.lifecycle.lifecycleScope
 import com.android.app.animation.Interpolators
+import com.android.internal.policy.SystemBarUtils
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.CrossFadeHelper
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.util.children
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.kotlin.stateFlow
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
+import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
-/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
+/** Binds a view-model to a [NotificationIconContainer]. */
 object NotificationIconContainerViewBinder {
+    @JvmStatic
     fun bind(
         view: NotificationIconContainer,
-        viewModel: NotificationIconContainerViewModel,
+        viewModel: NotificationIconContainerShelfViewModel,
+        configuration: ConfigurationState,
+        configurationController: ConfigurationController,
+        viewStore: ShelfNotificationIconViewStore,
+    ): DisposableHandle {
+        return view.repeatWhenAttached {
+            lifecycleScope.launch {
+                viewModel.icons.bindIcons(view, configuration, configurationController, viewStore)
+            }
+        }
+    }
+
+    @JvmStatic
+    fun bind(
+        view: NotificationIconContainer,
+        viewModel: NotificationIconContainerStatusBarViewModel,
+        configuration: ConfigurationState,
+        configurationController: ConfigurationController,
+        viewStore: StatusBarNotificationIconViewStore,
+    ): DisposableHandle {
+        val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
+        return view.repeatWhenAttached {
+            lifecycleScope.run {
+                launch {
+                    viewModel.icons.bindIcons(
+                        view,
+                        configuration,
+                        configurationController,
+                        viewStore
+                    )
+                }
+                launch { viewModel.iconColors.bindIconColors(view, contrastColorUtil) }
+                launch { viewModel.bindIsolatedIcon(view, viewStore) }
+                launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
+            }
+        }
+    }
+
+    @JvmStatic
+    fun bind(
+        view: NotificationIconContainer,
+        viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+        configuration: ConfigurationState,
         configurationController: ConfigurationController,
         dozeParameters: DozeParameters,
         featureFlags: FeatureFlagsClassic,
         screenOffAnimationController: ScreenOffAnimationController,
+        viewStore: IconViewStore,
     ): DisposableHandle {
         return view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
+            lifecycleScope.launch {
                 launch {
-                    viewModel.isDozing.collect { (isDozing, animate) ->
-                        val animateIfNotBlanking = animate && !dozeParameters.displayNeedsBlanking
-                        view.setDozing(isDozing, animateIfNotBlanking, /* delay= */ 0) {
-                            viewModel.completeDozeAnimation()
-                        }
-                    }
+                    viewModel.icons.bindIcons(
+                        view,
+                        configuration,
+                        configurationController,
+                        viewStore,
+                    )
                 }
-                // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+                launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
+                launch { viewModel.isDozing.bindIsDozing(view, dozeParameters) }
+                // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC
                 //  view-binder
                 launch {
-                    val iconAppearTranslation =
-                        view.resources.getConfigAwareDimensionPixelSize(
-                            this,
-                            configurationController,
-                            R.dimen.shelf_appear_translation,
-                        )
-                    bindVisibility(
-                        viewModel,
+                    viewModel.isVisible.bindIsVisible(
                         view,
+                        configuration,
                         featureFlags,
                         screenOffAnimationController,
-                        iconAppearTranslation,
-                    ) {
-                        viewModel.completeVisibilityAnimation()
+                    )
+                }
+                launch {
+                    configuration
+                        .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
+                        .bindIconColors(view)
+                }
+            }
+        }
+    }
+
+    /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
+    private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
+        collect(view::setAnimationsEnabled)
+    }
+
+    /**
+     * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
+     * of the [children] of an [NotificationIconContainer].
+     */
+    private suspend fun Flow<NotificationIconColorLookup>.bindIconColors(
+        view: NotificationIconContainer,
+        contrastColorUtil: ContrastColorUtil,
+    ) {
+        mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
+            .collect { iconLookup -> view.applyTint(iconLookup, contrastColorUtil) }
+    }
+
+    /**
+     * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
+     * of the [children] of an [NotificationIconContainer].
+     */
+    private suspend fun Flow<Int>.bindIconColors(view: NotificationIconContainer) {
+        collect { tint ->
+            view.children.filterIsInstance<StatusBarIconView>().forEach { icon ->
+                icon.staticDrawableColor = tint
+                icon.setDecorColor(tint)
+            }
+        }
+    }
+
+    private suspend fun Flow<AnimatedValue<Boolean>>.bindIsDozing(
+        view: NotificationIconContainer,
+        dozeParameters: DozeParameters,
+    ) {
+        collect { isDozing ->
+            if (isDozing.isAnimating) {
+                val animate = !dozeParameters.displayNeedsBlanking
+                view.setDozing(
+                    /* dozing = */ isDozing.value,
+                    /* fade = */ animate,
+                    /* delay = */ 0,
+                    /* endRunnable = */ isDozing::stopAnimating,
+                )
+            } else {
+                view.setDozing(
+                    /* dozing = */ isDozing.value,
+                    /* fade= */ false,
+                    /* delay= */ 0,
+                )
+            }
+        }
+    }
+
+    private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon(
+        view: NotificationIconContainer,
+        viewStore: IconViewStore,
+    ) {
+        coroutineScope {
+            launch {
+                isolatedIconLocation.collect { location ->
+                    view.setIsolatedIconLocation(location, true)
+                }
+            }
+            launch {
+                isolatedIcon.collect { iconInfo ->
+                    val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
+                    if (iconInfo.isAnimating) {
+                        view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
+                    } else {
+                        view.showIconIsolated(iconView)
                     }
                 }
             }
         }
     }
-    private suspend fun bindVisibility(
-        viewModel: NotificationIconContainerViewModel,
+
+    /** Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. */
+    private suspend fun Flow<NotificationIconsViewData>.bindIcons(
         view: NotificationIconContainer,
+        configuration: ConfigurationState,
+        configurationController: ConfigurationController,
+        viewStore: IconViewStore,
+    ): Unit = coroutineScope {
+        val iconSizeFlow: Flow<Int> =
+            configuration.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_icon_size_sp,
+            )
+        val iconHorizontalPaddingFlow: Flow<Int> =
+            configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
+        val statusBarHeightFlow: StateFlow<Int> =
+            stateFlow(changedSignals = configurationController.onConfigChanged) {
+                SystemBarUtils.getStatusBarHeight(view.context)
+            }
+        val layoutParams: Flow<FrameLayout.LayoutParams> =
+            combine(iconSizeFlow, iconHorizontalPaddingFlow, statusBarHeightFlow) {
+                iconSize,
+                iconHPadding,
+                statusBarHeight,
+                ->
+                FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
+            }
+
+        launch {
+            layoutParams.collect { params: FrameLayout.LayoutParams ->
+                for (child in view.children) {
+                    child.layoutParams = params
+                }
+            }
+        }
+
+        var prevIcons = NotificationIconsViewData()
+        sample(layoutParams, ::Pair).collect {
+            (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams),
+            ->
+            val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
+            prevIcons = iconsData
+
+            val replacingIcons =
+                iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) ->
+                    viewStore.iconView(v.notifKey)?.statusBarIcon
+                }
+            view.setReplacingIcons(replacingIcons)
+
+            val childrenByNotifKey: Map<String, StatusBarIconView> =
+                view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) {
+                    it.notification.key
+                }
+
+            iconsDiff.removed
+                .mapNotNull { key -> childrenByNotifKey[key] }
+                .forEach { child -> view.removeView(child) }
+
+            val toAdd = iconsDiff.added.mapNotNull { viewStore.iconView(it.notifKey) }
+            for ((i, sbiv) in toAdd.withIndex()) {
+                // The view might still be transiently added if it was just removed
+                // and added again
+                view.removeTransientView(sbiv)
+                view.addView(sbiv, i, layoutParams)
+            }
+
+            view.setChangingViewPositions(true)
+            // Re-sort notification icons
+            val childCount = view.childCount
+            for (i in 0 until childCount) {
+                val actual = view.getChildAt(i)
+                val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey)!!
+                if (actual === expected) {
+                    continue
+                }
+                view.removeView(expected)
+                view.addView(expected, i)
+            }
+            view.setChangingViewPositions(false)
+
+            view.setReplacingIcons(null)
+        }
+    }
+
+    // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this
+    //  can be moved there and cleaned up.
+    private fun ViewGroup.applyTint(
+        iconColors: NotificationIconColors,
+        contrastColorUtil: ContrastColorUtil,
+    ) {
+        children
+            .filterIsInstance<StatusBarIconView>()
+            .filter { it.width != 0 }
+            .forEach { iv -> iv.updateTintForIcon(iconColors, contrastColorUtil) }
+    }
+
+    private fun StatusBarIconView.updateTintForIcon(
+        iconColors: NotificationIconColors,
+        contrastColorUtil: ContrastColorUtil,
+    ) {
+        val isPreL = java.lang.Boolean.TRUE == getTag(R.id.icon_is_pre_L)
+        val isColorized = !isPreL || NotificationUtils.isGrayscale(this, contrastColorUtil)
+        staticDrawableColor = iconColors.staticDrawableColor(viewBounds, isColorized)
+        setDecorColor(iconColors.tint)
+    }
+
+    private suspend fun Flow<AnimatedValue<Boolean>>.bindIsVisible(
+        view: NotificationIconContainer,
+        configuration: ConfigurationState,
         featureFlags: FeatureFlagsClassic,
         screenOffAnimationController: ScreenOffAnimationController,
-        iconAppearTranslation: StateFlow<Int>,
-        onAnimationEnd: () -> Unit,
-    ) {
+    ): Unit = coroutineScope {
+        val iconAppearTranslation =
+            configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
         val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
-        viewModel.isVisible.collect { (isVisible, animate) ->
+        collect { isVisible ->
             view.animate().cancel()
+            val animatorListener =
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        isVisible.stopAnimating()
+                    }
+                }
             when {
-                !animate -> {
+                !isVisible.isAnimating -> {
                     view.alpha = 1f
                     if (!statusViewMigrated) {
                         view.translationY = 0f
                     }
-                    view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                    view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
                 }
                 featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
-                    animateInIconTranslation(view, statusViewMigrated)
-                    if (isVisible) {
-                        CrossFadeHelper.fadeIn(view, onAnimationEnd)
+                    view.animateInIconTranslation(statusViewMigrated)
+                    if (isVisible.value) {
+                        CrossFadeHelper.fadeIn(view, animatorListener)
                     } else {
-                        CrossFadeHelper.fadeOut(view, onAnimationEnd)
+                        CrossFadeHelper.fadeOut(view, animatorListener)
                     }
                 }
-                !isVisible -> {
+                !isVisible.value -> {
                     // Let's make sure the icon are translated to 0, since we cancelled it above
-                    animateInIconTranslation(view, statusViewMigrated)
-                    CrossFadeHelper.fadeOut(view, onAnimationEnd)
+                    view.animateInIconTranslation(statusViewMigrated)
+                    CrossFadeHelper.fadeOut(view, animatorListener)
                 }
                 view.visibility != View.VISIBLE -> {
                     // No fading here, let's just appear the icons instead!
                     view.visibility = View.VISIBLE
                     view.alpha = 1f
-                    appearIcons(
-                        view,
+                    view.appearIcons(
                         animate = screenOffAnimationController.shouldAnimateAodIcons(),
                         iconAppearTranslation.value,
                         statusViewMigrated,
+                        animatorListener,
                     )
-                    onAnimationEnd()
                 }
                 else -> {
                     // Let's make sure the icons are translated to 0, since we cancelled it above
-                    animateInIconTranslation(view, statusViewMigrated)
+                    view.animateInIconTranslation(statusViewMigrated)
                     // We were fading out, let's fade in instead
-                    CrossFadeHelper.fadeIn(view, onAnimationEnd)
+                    CrossFadeHelper.fadeIn(view, animatorListener)
                 }
             }
         }
     }
 
-    private fun appearIcons(
-        view: View,
+    private fun View.appearIcons(
         animate: Boolean,
         iconAppearTranslation: Int,
         statusViewMigrated: Boolean,
+        animatorListener: Animator.AnimatorListener,
     ) {
         if (animate) {
             if (!statusViewMigrated) {
-                view.translationY = -iconAppearTranslation.toFloat()
+                translationY = -iconAppearTranslation.toFloat()
             }
-            view.alpha = 0f
-            animateInIconTranslation(view, statusViewMigrated)
-            view
-                .animate()
+            alpha = 0f
+            animate()
                 .alpha(1f)
                 .setInterpolator(Interpolators.LINEAR)
                 .setDuration(AOD_ICONS_APPEAR_DURATION)
+                .apply { if (statusViewMigrated) animateInIconTranslation() }
+                .setListener(animatorListener)
                 .start()
         } else {
-            view.alpha = 1.0f
+            alpha = 1.0f
             if (!statusViewMigrated) {
-                view.translationY = 0f
+                translationY = 0f
             }
         }
     }
 
-    private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) {
+    private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
         if (!statusViewMigrated) {
-            view
-                .animate()
-                .setInterpolator(Interpolators.DECELERATE_QUINT)
-                .translationY(0f)
-                .setDuration(AOD_ICONS_APPEAR_DURATION)
-                .start()
+            animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
         }
     }
 
+    private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
+        setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
+
+    /** External storage for [StatusBarIconView] instances. */
+    fun interface IconViewStore {
+        fun iconView(key: String): StatusBarIconView?
+    }
+
     private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+    @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
 }
 
-fun Resources.getConfigAwareDimensionPixelSize(
-    scope: CoroutineScope,
-    configurationController: ConfigurationController,
-    @DimenRes id: Int,
-): StateFlow<Int> =
-    scope.stateFlow(
-        changedSignals = configurationController.onDensityOrFontScaleChanged,
-        getValue = { getDimensionPixelSize(id) }
-    )
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore
+@Inject
+constructor(
+    private val notifCollection: NotifCollection,
+) : IconViewStore {
+    override fun iconView(key: String): StatusBarIconView? =
+        notifCollection.getEntry(key)?.icons?.shelfIcon
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(
+    private val notifCollection: NotifCollection,
+) : IconViewStore {
+    override fun iconView(key: String): StatusBarIconView? =
+        notifCollection.getEntry(key)?.icons?.aodIcon
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore
+@Inject
+constructor(
+    private val notifCollection: NotifCollection,
+) : IconViewStore {
+    override fun iconView(key: String): StatusBarIconView? =
+        notifCollection.getEntry(key)?.icons?.statusBarIcon
+}
+
+private val View.viewBounds: Rect
+    get() {
+        val tmpArray = intArrayOf(0, 0)
+        getLocationOnScreen(tmpArray)
+        return Rect(
+            /* left = */ tmpArray[0],
+            /* top = */ tmpArray[1],
+            /* right = */ left + width,
+            /* bottom = */ top + height,
+        )
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
new file mode 100644
index 0000000..97d1e1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import android.graphics.Rect
+
+/**
+ * Lookup the colors to use for the notification icons based on the bounds of the icon container. A
+ * result of `null` indicates that no color changes should be applied.
+ */
+fun interface NotificationIconColorLookup {
+    fun iconColors(viewBounds: Rect): NotificationIconColors?
+}
+
+/** Colors to apply to notification icons. */
+interface NotificationIconColors {
+
+    /** A tint to apply to the icons. */
+    val tint: Int
+
+    /**
+     * Returns the color to be applied to an icon, based on that icon's view bounds and whether or
+     * not the notification icon is colorized.
+     */
+    fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 3289a3c..611ed89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import android.graphics.Rect
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -25,6 +26,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.util.kotlin.pairwise
@@ -32,9 +34,9 @@
 import com.android.systemui.util.ui.AnimatableEvent
 import com.android.systemui.util.ui.AnimatedValue
 import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.util.ui.zip
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -47,17 +49,16 @@
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val featureFlags: FeatureFlagsClassic,
+    iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
     screenOffAnimationController: ScreenOffAnimationController,
     shadeInteractor: ShadeInteractor,
-) : NotificationIconContainerViewModel {
+) {
 
-    private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-    private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
-    override val animationsEnabled: Flow<Boolean> =
+    /** Are changes to the icon container animated? */
+    val animationsEnabled: Flow<Boolean> =
         combine(
             shadeInteractor.isShadeTouchable,
             keyguardInteractor.isKeyguardVisible,
@@ -65,7 +66,8 @@
             panelTouchesEnabled && isKeyguardVisible
         }
 
-    override val isDozing: Flow<AnimatedValue<Boolean>> =
+    /** Should icons be rendered in "dozing" mode? */
+    val isDozing: Flow<AnimatedValue<Boolean>> =
         keyguardTransitionInteractor.startedKeyguardTransitionStep
             // Determine if we're dozing based on the most recent transition
             .map { step: TransitionStep ->
@@ -80,9 +82,10 @@
                 AnimatableEvent(isDozing, animate)
             }
             .distinctUntilChanged()
-            .toAnimatedValueFlow(completionEvents = onDozeAnimationComplete)
+            .toAnimatedValueFlow()
 
-    override val isVisible: Flow<AnimatedValue<Boolean>> =
+    /** Is the icon container visible? */
+    val isVisible: Flow<AnimatedValue<Boolean>> =
         combine(
                 keyguardTransitionInteractor.finishedKeyguardState.map { it != KeyguardState.GONE },
                 deviceEntryInteractor.isBypassEnabled,
@@ -90,46 +93,51 @@
                 isPulseExpandingAnimated(),
             ) {
                 onKeyguard: Boolean,
-                bypassEnabled: Boolean,
-                (notifsFullyHidden: Boolean, isAnimatingHide: Boolean),
-                (pulseExpanding: Boolean, isAnimatingPulse: Boolean),
+                isBypassEnabled: Boolean,
+                notifsFullyHidden: AnimatedValue<Boolean>,
+                pulseExpanding: AnimatedValue<Boolean>,
                 ->
-                val isAnimating = isAnimatingHide || isAnimatingPulse
                 when {
                     // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
                     // animation is playing, in which case we want them to be visible if we're
                     // animating in the AOD UI and will be switching to KEYGUARD shortly.
                     !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
-                        AnimatedValue(false, isAnimating = false)
-                    // If we're bypassing, then we're visible
-                    bypassEnabled -> AnimatedValue(true, isAnimating)
-                    // If we are pulsing (and not bypassing), then we are hidden
-                    pulseExpanding -> AnimatedValue(false, isAnimating)
-                    // If notifs are fully gone, then we're visible
-                    notifsFullyHidden -> AnimatedValue(true, isAnimating)
-                    // Otherwise, we're hidden
-                    else -> AnimatedValue(false, isAnimating)
+                        AnimatedValue.NotAnimating(false)
+                    else ->
+                        zip(notifsFullyHidden, pulseExpanding) {
+                            areNotifsFullyHidden,
+                            isPulseExpanding,
+                            ->
+                            when {
+                                // If we're bypassing, then we're visible
+                                isBypassEnabled -> true
+                                // If we are pulsing (and not bypassing), then we are hidden
+                                isPulseExpanding -> false
+                                // If notifs are fully gone, then we're visible
+                                areNotifsFullyHidden -> true
+                                // Otherwise, we're hidden
+                                else -> false
+                            }
+                        }
                 }
             }
             .distinctUntilChanged()
 
-    override fun completeDozeAnimation() {
-        onDozeAnimationComplete.tryEmit(Unit)
-    }
-
-    override fun completeVisibilityAnimation() {
-        onVisAnimationComplete.tryEmit(Unit)
-    }
+    /** [NotificationIconsViewData] indicating which icons to display in the view. */
+    val icons: Flow<NotificationIconsViewData> =
+        iconsInteractor.aodNotifs.map { entries ->
+            NotificationIconsViewData(
+                visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
+            )
+        }
 
     /** Is there an expanded pulse, are we animating in response? */
     private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
         return notificationsKeyguardInteractor.isPulseExpanding
             .pairwise(initialValue = null)
             // If pulsing changes, start animating, unless it's the first emission
-            .map { (prev, expanding) ->
-                AnimatableEvent(expanding!!, startAnimating = prev != null)
-            }
-            .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+            .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
+            .toAnimatedValueFlow()
     }
 
     /** Are notifications completely hidden from view, are we animating in response? */
@@ -151,10 +159,14 @@
                         // We only want the appear animations to happen when the notifications
                         // get fully hidden, since otherwise the un-hide animation overlaps.
                         featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
-                        else -> fullyHidden!!
+                        else -> fullyHidden
                     }
-                AnimatableEvent(fullyHidden!!, animate)
+                AnimatableEvent(fullyHidden, animate)
             }
-            .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+            .toAnimatedValueFlow()
+    }
+
+    private class IconColorsImpl(override val tint: Int) : NotificationIconColors {
+        override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index c44a2b6..1560106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -15,18 +15,22 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
-import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 /** View-model for the overflow row of notification icons displayed in the notification shade. */
-class NotificationIconContainerShelfViewModel @Inject constructor() :
-    NotificationIconContainerViewModel {
-    override val animationsEnabled: Flow<Boolean> = flowOf(true)
-    override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
-    override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
-    override fun completeDozeAnimation() {}
-    override fun completeVisibilityAnimation() {}
+class NotificationIconContainerShelfViewModel
+@Inject
+constructor(
+    interactor: NotificationIconsInteractor,
+) {
+    /** [NotificationIconsViewData] indicating which icons to display in the view. */
+    val icons: Flow<NotificationIconsViewData> =
+        interactor.filteredNotifSet().map { entries ->
+            NotificationIconsViewData(
+                visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) },
+            )
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 035687a..53631e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -15,22 +15,39 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import android.graphics.Rect
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
 import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 
 /** View-model for the row of notification icons displayed in the status bar, */
 class NotificationIconContainerStatusBarViewModel
 @Inject
 constructor(
+    darkIconInteractor: DarkIconInteractor,
+    iconsInteractor: StatusBarNotificationIconsInteractor,
+    headsUpIconInteractor: HeadsUpNotificationIconInteractor,
     keyguardInteractor: KeyguardInteractor,
+    notificationsInteractor: ActiveNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
-) : NotificationIconContainerViewModel {
-    override val animationsEnabled: Flow<Boolean> =
+) {
+
+    /** Are changes to the icon container animated? */
+    val animationsEnabled: Flow<Boolean> =
         combine(
             shadeInteractor.isShadeTouchable,
             keyguardInteractor.isKeyguardShowing,
@@ -38,8 +55,67 @@
             panelTouchesEnabled && !isKeyguardShowing
         }
 
-    override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
-    override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
-    override fun completeDozeAnimation() {}
-    override fun completeVisibilityAnimation() {}
+    /** The colors with which to display the notification icons. */
+    val iconColors: Flow<NotificationIconColorLookup> =
+        combine(
+            darkIconInteractor.tintAreas,
+            darkIconInteractor.tintColor,
+            // Included so that tints are re-applied after entries are changed.
+            notificationsInteractor.notifications,
+        ) { areas, tint, _ ->
+            NotificationIconColorLookup { viewBounds: Rect ->
+                if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+                    IconColorsImpl(tint, areas)
+                } else {
+                    null
+                }
+            }
+        }
+
+    /** [NotificationIconsViewData] indicating which icons to display in the view. */
+    val icons: Flow<NotificationIconsViewData> =
+        iconsInteractor.statusBarNotifs.map { entries ->
+            NotificationIconsViewData(
+                visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
+            )
+        }
+
+    /** An Icon to show "isolated" in the IconContainer. */
+    val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
+        headsUpIconInteractor.isolatedNotification
+            .pairwise(initialValue = null)
+            .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) {
+                (prev, isolatedNotif),
+                (iconsViewData, shadeExpansion),
+                ->
+                val iconInfo =
+                    isolatedNotif?.let {
+                        iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
+                    }
+                val animate =
+                    when {
+                        isolatedNotif == prev -> false
+                        isolatedNotif == null || prev == null -> shadeExpansion == 0f
+                        else -> false
+                    }
+                AnimatableEvent(iconInfo, animate)
+            }
+            .toAnimatedValueFlow()
+
+    /** Location to show an isolated icon, if there is one. */
+    val isolatedIconLocation: Flow<Rect> =
+        headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+
+    private class IconColorsImpl(
+        override val tint: Int,
+        private val areas: Collection<Rect>,
+    ) : NotificationIconColors {
+        override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int {
+            return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+                tint
+            } else {
+                DarkIconDispatcher.DEFAULT_ICON_TINT
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
deleted file mode 100644
index 65eb220..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.icon.ui.viewmodel
-
-import com.android.systemui.util.ui.AnimatedValue
-import kotlinx.coroutines.flow.Flow
-
-/**
- * View-model for the row of notification icons displayed in the NotificationShelf, StatusBar, and
- * AOD.
- */
-interface NotificationIconContainerViewModel {
-    /** Are changes to the icon container animated? */
-    val animationsEnabled: Flow<Boolean>
-
-    /** Should icons be rendered in "dozing" mode? */
-    val isDozing: Flow<AnimatedValue<Boolean>>
-
-    /** Is the icon container visible? */
-    val isVisible: Flow<AnimatedValue<Boolean>>
-
-    /**
-     * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
-     * property was `true`, calling this method will update it to `false.
-     */
-    fun completeDozeAnimation()
-
-    /**
-     * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
-     * property was `true`, calling this method will update it to `false.
-     */
-    fun completeVisibilityAnimation()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt
new file mode 100644
index 0000000..867be84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.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.icon.ui.viewmodel
+
+import android.graphics.drawable.Icon
+import androidx.collection.ArrayMap
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+
+/** Encapsulates the collection of notification icons present on the device. */
+data class NotificationIconsViewData(
+    /** Icons that are visible in the container. */
+    val visibleKeys: List<NotificationIconInfo> = emptyList(),
+    /** Keys of icons that are "behind" the overflow dot. */
+    val collapsedKeys: Set<String> = emptySet(),
+    /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */
+    val forceShowDot: Boolean = false,
+) {
+    /** The difference between two [NotificationIconsViewData]s. */
+    data class Diff(
+        /** Icons added in the newer dataset. */
+        val added: List<NotificationIconInfo> = emptyList(),
+        /** Icons removed from the older dataset. */
+        val removed: List<String> = emptyList(),
+        /**
+         * Groups whose icon was replaced with a single new notification icon. The key of the [Map]
+         * is the notification group key, and the value is the new icon.
+         *
+         * Specifically, this models a difference where the older dataset had notification groups
+         * with a single icon in the set, and the newer dataset has a single, different icon for the
+         * same group. A view binder can use this information for special animations for this
+         * specific change.
+         */
+        val groupReplacements: Map<String, NotificationIconInfo> = emptyMap(),
+    )
+
+    companion object {
+        /**
+         * Returns an [NotificationIconsViewData.Diff] calculated from a [new] and [previous][prev]
+         * [NotificationIconsViewData] state.
+         */
+        fun computeDifference(
+            new: NotificationIconsViewData,
+            prev: NotificationIconsViewData
+        ): Diff {
+            val added: List<NotificationIconInfo> =
+                new.visibleKeys.filter {
+                    it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey }
+                }
+            val removed: List<NotificationIconInfo> =
+                prev.visibleKeys.filter {
+                    it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey }
+                }
+            val groupsToShow: Set<IconGroupInfo> =
+                new.visibleKeys.asSequence().map { it.groupInfo }.toSet()
+            val replacements: ArrayMap<String, NotificationIconInfo> =
+                removed
+                    .asSequence()
+                    .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow }
+                    .groupBy { it.groupInfo.groupKey }
+                    .mapValuesNotNullTo(ArrayMap()) { (_, vs) ->
+                        vs.takeIf { it.size == 1 }?.get(0)
+                    }
+            return Diff(added, removed.map { it.notifKey }, replacements)
+        }
+    }
+}
+
+/** An Icon, and keys for unique identification. */
+data class NotificationIconInfo(
+    val sourceIcon: Icon,
+    val notifKey: String,
+    val groupKey: String,
+)
+
+/**
+ * Construct an [NotificationIconInfo] out of an [ActiveNotificationModel], or return `null` if one
+ * cannot be created due to missing information.
+ */
+fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): NotificationIconInfo? {
+    return sourceIcon?.let {
+        groupKey?.let { groupKey ->
+            NotificationIconInfo(
+                sourceIcon = sourceIcon,
+                notifKey = key,
+                groupKey = groupKey,
+            )
+        }
+    }
+}
+
+private val NotificationIconInfo.groupInfo: IconGroupInfo
+    get() = IconGroupInfo(sourceIcon, groupKey)
+
+private data class IconGroupInfo(
+    val sourceIcon: Icon,
+    val groupKey: String,
+) {
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as IconGroupInfo
+
+        if (groupKey != other.groupKey) return false
+        return sourceIcon.sameAs(other.sourceIcon)
+    }
+
+    override fun hashCode(): Int {
+        var result = groupKey.hashCode()
+        result = 31 * result + sourceIcon.type.hashCode()
+        when (sourceIcon.type) {
+            Icon.TYPE_BITMAP,
+            Icon.TYPE_ADAPTIVE_BITMAP -> {
+                result = 31 * result + sourceIcon.bitmap.hashCode()
+            }
+            Icon.TYPE_DATA -> {
+                result = 31 * result + sourceIcon.dataLength.hashCode()
+                result = 31 * result + sourceIcon.dataOffset.hashCode()
+            }
+            Icon.TYPE_RESOURCE -> {
+                result = 31 * result + sourceIcon.resId.hashCode()
+                result = 31 * result + sourceIcon.resPackage.hashCode()
+            }
+            Icon.TYPE_URI,
+            Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+                result = 31 * result + sourceIcon.uriString.hashCode()
+            }
+        }
+        return result
+    }
+}
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 b2c32cd..db7f46e 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
@@ -1,6 +1,5 @@
 package com.android.systemui.statusbar.notification.interruption
 
-import android.app.Notification
 import android.app.Notification.VISIBILITY_SECRET
 import android.content.Context
 import android.database.ContentObserver
@@ -14,6 +13,8 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -78,7 +79,8 @@
     private val statusBarStateController: SysuiStatusBarStateController,
     private val userTracker: UserTracker,
     private val secureSettings: SecureSettings,
-    private val globalSettings: GlobalSettings
+    private val globalSettings: GlobalSettings,
+    private val featureFlags: FeatureFlagsClassic
 ) : CoreStartable, KeyguardNotificationVisibilityProvider {
     private val showSilentNotifsUri =
             secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
@@ -201,7 +203,7 @@
             // device isn't public, no need to check public-related settings, so allow
             !lockscreenUserManager.isLockscreenPublicMode(user) -> false
             // entry is meant to be secret on the lockscreen, disallow
-            entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true
+            isRankingVisibilitySecret(entry) -> true
             // disallow if user disallows notifications in public
             else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
         }
@@ -215,6 +217,17 @@
         }
     }
 
+    private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean {
+        return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
+            // info, and NotificationLockscreenUserManagerImpl is already listening for updates
+            // to those
+            entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
+        } else {
+            entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
+        }
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
         println("isLockedOrLocking=$isLockedOrLocking")
         withIncreasedIndent {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 4a823a4..2fffd37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -239,11 +239,11 @@
         })
     }
 
-    fun logNoPulsingNotificationHidden(entry: NotificationEntry) {
+    fun logNoPulsingNotificationHiddenOverride(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
         }, {
-            "No pulsing: notification hidden on lock screen: $str1"
+            "No pulsing: notification hidden on lock screen by override: $str1"
         })
     }
 
@@ -290,11 +290,11 @@
         })
     }
 
-    fun keyguardHideNotification(entry: NotificationEntry) {
+    fun logNoAlertingNotificationHidden(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
         }, {
-            "Keyguard Hide Notification: $str1"
+            "No alerting: notification hidden on lock screen: $str1"
         })
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 6ec9dbe..b0155f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -180,4 +180,9 @@
      * Add a component that can suppress visual interruptions.
      */
     void addSuppressor(NotificationInterruptSuppressor suppressor);
+
+    /**
+     * Remove a component that can suppress visual interruptions.
+     */
+    void removeSuppressor(NotificationInterruptSuppressor suppressor);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 3819843..301ddbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -175,6 +175,11 @@
     }
 
     @Override
+    public void removeSuppressor(NotificationInterruptSuppressor suppressor) {
+        mSuppressors.remove(suppressor);
+    }
+
+    @Override
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.getSbn();
 
@@ -505,7 +510,7 @@
 
         if (entry.getRanking().getLockscreenVisibilityOverride()
                 == Notification.VISIBILITY_PRIVATE) {
-            if (log) mLogger.logNoPulsingNotificationHidden(entry);
+            if (log) mLogger.logNoPulsingNotificationHiddenOverride(entry);
             return false;
         }
 
@@ -536,7 +541,7 @@
         }
 
         if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
-            if (log) mLogger.keyguardHideNotification(entry);
+            if (log) mLogger.logNoAlertingNotificationHidden(entry);
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
index ebdeded..d7f0baf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -58,6 +58,10 @@
         wrapped.addSuppressor(suppressor)
     }
 
+    override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+        wrapped.removeSuppressor(suppressor)
+    }
+
     override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
         wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index 454ba02..920bbe9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -60,6 +60,13 @@
     fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor)
 
     /**
+     * Removes a [component][suppressor] that can suppress visual interruptions.
+     *
+     * @param[suppressor] the suppressor to remove
+     */
+    fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor)
+
+    /**
      * Decides whether a [notification][entry] should display as heads-up or not, but does not log
      * that decision.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
new file mode 100644
index 0000000..4ef80e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.interruption
+
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * A reason why visual interruptions might be suppressed.
+ *
+ * @see VisualInterruptionCondition
+ * @see VisualInterruptionFilter
+ */
+enum class VisualInterruptionType {
+    /* HUN when awake */
+    PEEK,
+
+    /* HUN when dozing */
+    PULSE,
+
+    /* Bubble */
+    BUBBLE
+}
+
+/**
+ * A reason why visual interruptions might be suppressed.
+ *
+ * @see VisualInterruptionCondition
+ * @see VisualInterruptionFilter
+ */
+sealed interface VisualInterruptionSuppressor {
+    /** The type(s) of interruption that this suppresses. */
+    val types: Set<VisualInterruptionType>
+
+    /** A human-readable string to be logged to explain why this suppressed an interruption. */
+    val reason: String
+
+    /** An optional UiEvent ID to be recorded when this suppresses an interruption. */
+    val uiEventId: UiEventEnum?
+}
+
+/** A reason why visual interruptions might be suppressed regardless of the notification. */
+abstract class VisualInterruptionCondition(
+    override val types: Set<VisualInterruptionType>,
+    override val reason: String,
+    override val uiEventId: UiEventEnum? = null
+) : VisualInterruptionSuppressor {
+    /** @return true if these interruptions should be suppressed right now. */
+    abstract fun shouldSuppress(): Boolean
+}
+
+/** A reason why visual interruptions might be suppressed based on the notification. */
+abstract class VisualInterruptionFilter(
+    override val types: Set<VisualInterruptionType>,
+    override val reason: String,
+    override val uiEventId: UiEventEnum? = null
+) : VisualInterruptionSuppressor {
+    /**
+     * @param entry the notification to consider suppressing
+     * @return true if these interruptions should be suppressed for this notification right now
+     */
+    abstract fun shouldSuppress(entry: NotificationEntry): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 847d948..661768d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -351,12 +351,21 @@
 
     @Override
     public long performRemoveAnimation(long duration, long delay, float translationDirection,
-            boolean isHeadsUpAnimation, Runnable onFinishedRunnable,
+            boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
-        startAppearAnimation(false /* isAppearing */, translationDirection,
-                delay, duration, onFinishedRunnable, animationListener);
+        if (mDrawingAppearAnimation) {
+            startAppearAnimation(false /* isAppearing */, translationDirection,
+                    delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+        } else {
+            if (onStartedRunnable != null) {
+                onStartedRunnable.run();
+            }
+            if (onFinishedRunnable != null) {
+                onFinishedRunnable.run();
+            }
+        }
         return 0;
     }
 
@@ -365,12 +374,14 @@
             Runnable onFinishRunnable) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAppear;
-        startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
-                duration, null, null);
+        if (mDrawingAppearAnimation) {
+            startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
+                    duration, null, null, null);
+        }
     }
 
     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
-            long duration, final Runnable onFinishedRunnable,
+            long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         mAnimationTranslationY = translationDirection * getActualHeight();
         cancelAppearAnimation();
@@ -434,6 +445,9 @@
 
             @Override
             public void onAnimationStart(Animator animation) {
+                if (onStartedRunnable != null) {
+                    onStartedRunnable.run();
+                }
                 mRunWithoutInterruptions = true;
                 Configuration.Builder builder = Configuration.Builder
                         .withView(getCujType(isAppearing), ActivatableNotificationView.this);
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 061132f..11c65e5 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
@@ -27,7 +27,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.Notification;
-import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -40,8 +39,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Trace;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.IndentingPrintWriter;
@@ -73,15 +70,15 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -274,8 +271,8 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private final ViewRefactorFlag mInlineReplyAnimation =
-            new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
+    private final RefactorFlag mInlineReplyAnimation =
+            RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -1904,16 +1901,7 @@
             return traceTag;
         }
 
-        if (isSummaryWithChildren()) {
-            return traceTag + "(summary)";
-        }
-        Class<? extends Notification.Style> style =
-                getEntry().getSbn().getNotification().getNotificationStyle();
-        if (style == null) {
-            return traceTag + "(nostyle)";
-        } else {
-            return traceTag + "(" + style.getSimpleName() + ")";
-        }
+        return  traceTag + "(" + getEntry().getNotificationStyle() + ")";
     }
 
     @Override
@@ -2930,6 +2918,7 @@
             long delay,
             float translationDirection,
             boolean isHeadsUpAnimation,
+            Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
@@ -2937,10 +2926,16 @@
             if (anim != null) {
                 anim.addListener(new AnimatorListenerAdapter() {
                     @Override
+                    public void onAnimationStart(Animator animation) {
+                        if (onStartedRunnable != null) {
+                            onStartedRunnable.run();
+                        }
+                    }
+                    @Override
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                onFinishedRunnable, animationListener);
+                                null, onFinishedRunnable, animationListener);
                     }
                 });
                 anim.start();
@@ -2948,7 +2943,7 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2599231..2a3e69b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,9 +28,9 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
-import com.android.systemui.res.R;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.util.DumpUtilsKt;
@@ -49,8 +49,8 @@
     private float mOutlineAlpha = -1f;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
-    protected final ViewRefactorFlag mImprovedHunAnimation =
-            new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS);
+    protected final RefactorFlag mImprovedHunAnimation =
+            RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS);
 
     /**
      * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f2f55a8..6edab4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -69,6 +69,9 @@
     private boolean mClipToActualHeight = true;
     private boolean mChangingPosition = false;
     private ViewGroup mTransientContainer;
+
+    // Needs to be added as transient view when removed from parent, because it's in animation
+    private boolean mInRemovalAnimation;
     private boolean mInShelf;
     private boolean mTransformingInShelf;
     protected float mContentTransformationAmount;
@@ -381,6 +384,7 @@
      */
     public abstract long performRemoveAnimation(long duration,
             long delay, float translationDirection, boolean isHeadsUpAnimation,
+            Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
@@ -604,6 +608,25 @@
     }
 
     /**
+     * Add the view to a transient container.
+     */
+    public void addToTransientContainer(ViewGroup container, int index) {
+        container.addTransientView(this, index);
+        setTransientContainer(container);
+    }
+
+    /**
+     * @return If the view is in a process of removal animation.
+     */
+    public boolean inRemovalAnimation() {
+        return mInRemovalAnimation;
+    }
+
+    public void setInRemovalAnimation(boolean inRemovalAnimation) {
+        mInRemovalAnimation = inRemovalAnimation;
+    }
+
+    /**
      * @return true if the group's expansion state is changing, false otherwise.
      */
     public boolean isGroupExpansionChanging() {
@@ -837,6 +860,7 @@
                 pw.println();
             }
             if (DUMP_VERBOSE) {
+                pw.println("mInRemovalAnimation: " + mInRemovalAnimation);
                 pw.println("mClipTopAmount: " + mClipTopAmount);
                 pw.println("mClipBottomAmount " + mClipBottomAmount);
                 pw.println("mClipToActualHeight: " + mClipToActualHeight);
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 a27a305..60e75ff 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
@@ -1363,12 +1363,12 @@
                         result.mController.setPendingIntent(existingPendingIntent);
                     }
                     if (result.mController.updatePendingIntentFromActions(actions)) {
-                        if (!result.mView.isActive()) {
-                            result.mView.focus();
+                        if (!result.mController.isActive()) {
+                            result.mController.focus();
                         }
                     } else {
-                        if (result.mView.isActive()) {
-                            result.mView.close();
+                        if (result.mController.isActive()) {
+                            result.mController.close();
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index e200b90..aabf295 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -237,9 +237,13 @@
     @Override
     public long performRemoveAnimation(long duration, long delay,
             float translationDirection, boolean isHeadsUpAnimation,
+            Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         // TODO: Use duration
+        if (onStartedRunnable != null) {
+            onStartedRunnable.run();
+        }
         setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run());
         return 0;
     }
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
new file mode 100644
index 0000000..78370ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import android.graphics.drawable.Icon
+
+/** Model for entries in the notification stack. */
+data class ActiveNotificationModel(
+    /** Notification key associated with this entry. */
+    val key: String,
+    /** Notification group key associated with this entry. */
+    val groupKey: String?,
+    /** Is this entry in the ambient / minimized section (lowest priority)? */
+    val isAmbient: Boolean,
+    /**
+     * Is this entry dismissed? This is `true` when the user has dismissed the notification in the
+     * UI, but `NotificationManager` has not yet signalled to us that it has received the dismissal.
+     */
+    val isRowDismissed: Boolean,
+    /** Is this entry in the silent section? */
+    val isSilent: Boolean,
+    /**
+     * Does this entry represent a conversation, the last message of which was from a remote input
+     * reply?
+     */
+    val isLastMessageFromReply: Boolean,
+    /** Is this entry suppressed from appearing in the status bar as an icon? */
+    val isSuppressedFromStatusBar: Boolean,
+    /** Is this entry actively pulsing on AOD or bypassed-keyguard? */
+    val isPulsing: Boolean,
+    /** Icon to display on AOD. */
+    val aodIcon: Icon?,
+    /** Icon to display in the notification shelf. */
+    val shelfIcon: Icon?,
+    /** Icon to display in the status bar. */
+    val statusBarIcon: Icon?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
new file mode 100644
index 0000000..dee609c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.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.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the NotificationIconContainer refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationIconContainerRefactor {
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR
+
+    /** Is the refactor enabled? */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationsIconContainerRefactor()
+
+    /**
+     * 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/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index b92c51f..d35e4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -19,19 +19,23 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.policy.ConfigurationController
 import javax.inject.Inject
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
@@ -75,14 +79,25 @@
     fun bind(
         shelf: NotificationShelf,
         viewModel: NotificationShelfViewModel,
+        configuration: ConfigurationState,
+        configurationController: ConfigurationController,
         falsingManager: FalsingManager,
-        featureFlags: FeatureFlags,
         notificationIconAreaController: NotificationIconAreaController,
+        shelfIconViewStore: ShelfNotificationIconViewStore,
     ) {
         ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
         shelf.apply {
-            // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
-            notificationIconAreaController.setShelfIcons(shelfIcons)
+            if (NotificationIconContainerRefactor.isEnabled) {
+                NotificationIconContainerViewBinder.bind(
+                    shelfIcons,
+                    viewModel.icons,
+                    configuration,
+                    configurationController,
+                    shelfIconViewStore,
+                )
+            } else {
+                notificationIconAreaController.setShelfIcons(shelfIcons)
+            }
             repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 5ca8b53..64b5b62c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
 import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
 import javax.inject.Inject
@@ -31,6 +32,7 @@
 constructor(
     private val interactor: NotificationShelfInteractor,
     activatableViewModel: ActivatableNotificationViewModel,
+    val icons: NotificationIconContainerShelfViewModel,
 ) : ActivatableNotificationViewModel by activatableViewModel {
     /** Is the shelf allowed to be clickable when it has content? */
     val isClickable: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5d46f52..bae5baa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -25,9 +25,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.row.ExpandableView
 
-/**
- * Root view to insert Lock screen media controls into the notification stack.
- */
+/** Root view to insert Lock screen media controls into the notification stack. */
 class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
 
     override var clipHeight = 0
@@ -46,8 +44,8 @@
     }
 
     private fun updateResources() {
-        cornerRadius = context.resources
-                .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+        cornerRadius =
+            context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
     }
 
     public override fun updateClipping() {
@@ -70,18 +68,23 @@
     }
 
     override fun performRemoveAnimation(
-            duration: Long,
-            delay: Long,
-            translationDirection: Float,
-            isHeadsUpAnimation: Boolean,
-            onFinishedRunnable: Runnable?,
-            animationListener: AnimatorListenerAdapter?
+        duration: Long,
+        delay: Long,
+        translationDirection: Float,
+        isHeadsUpAnimation: Boolean,
+        onStartedRunnable: Runnable?,
+        onFinishedRunnable: Runnable?,
+        animationListener: AnimatorListenerAdapter?
     ): Long {
         return 0
     }
 
-    override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean,
-                                     onEnd: Runnable?) {
+    override fun performAddAnimation(
+        delay: Long,
+        duration: Long,
+        isHeadsUpAppear: Boolean,
+        onEnd: Runnable?
+    ) {
         // No animation, it doesn't need it, this would be local
     }
-}
\ No newline at end of file
+}
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 e4d96c3..6bb9573 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
@@ -67,18 +67,6 @@
     void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer);
 
     /**
-     * Generate an animation for an added child view.
-     *  @param child The view to be added.
-     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
-     */
-    void generateAddAnimation(ExpandableView child, boolean fromMoreCard);
-
-    /**
-     * Generate a child order changed event.
-     */
-    void generateChildOrderChangedEvent();
-
-    /**
      * Returns the number of children in the NotificationListContainer.
      *
      * @return the number of children in the NotificationListContainer
@@ -187,21 +175,6 @@
     default void bindRow(ExpandableNotificationRow row) {}
 
     /**
-     * Does this list contain a given view. True by default is fine, since we only ask this if the
-     * view has a parent.
-     */
-    default boolean containsView(View v) {
-        return true;
-    }
-
-    /**
-     * Tells the container that an animation is about to expand it.
-     */
-    default void setWillExpand(boolean willExpand) {}
-
-    void setNotificationActivityStarter(NotificationActivityStarter notificationActivityStarter);
-
-    /**
      * @return the start location where we start clipping notifications.
      */
     default int getTopClippingStartLocation() {
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 10b1bcd..1e9cfa8 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
@@ -20,6 +20,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
+import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.util.DumpUtilsKt.println;
@@ -88,7 +89,7 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.res.R;
@@ -106,6 +107,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -200,8 +202,8 @@
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
     private final boolean mSensitiveRevealAnimEndabled;
-    private final ViewRefactorFlag mAnimatedInsets;
-    private final ViewRefactorFlag mShelfRefactor;
+    private final RefactorFlag mAnimatedInsets;
+    private final RefactorFlag mShelfRefactor;
 
     private final boolean mNewAodTransition;
 
@@ -323,7 +325,6 @@
     private NotificationsController mNotificationsController;
     private ActivityStarter mActivityStarter;
     private final int[] mTempInt2 = new int[2];
-    private boolean mGenerateChildOrderChangedEvent;
     private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
     private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
     private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
@@ -576,6 +577,7 @@
         mSplitShadeStateController = splitShadeStateController;
         updateSplitNotificationShade();
     }
+    private FeatureFlags mFeatureFlags;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -628,16 +630,16 @@
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
-        FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
-        mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled(
+        mFeatureFlags = Dependency.get(FeatureFlags.class);
+        mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled(
                 Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
-        mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
-        mNewAodTransition = featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION);
-        mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
+        mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
+        mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION);
+        mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
+        mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         mAnimatedInsets =
-                new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
-        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
+                new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
+        mShelfRefactor = new RefactorFlag(mFeatureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -693,7 +695,9 @@
         super.onFinishInflate();
 
         inflateEmptyShadeView();
-        inflateFooterView();
+        if (!FooterViewRefactor.isEnabled()) {
+            inflateFooterView();
+        }
     }
 
     /**
@@ -728,9 +732,11 @@
     }
 
     void reinflateViews() {
-        inflateFooterView();
+        if (!FooterViewRefactor.isEnabled()) {
+            inflateFooterView();
+            updateFooter();
+        }
         inflateEmptyShadeView();
-        updateFooter();
         mSectionsManager.reinflateViews();
     }
 
@@ -745,7 +751,7 @@
 
     @VisibleForTesting
     public void updateFooter() {
-        if (mFooterView == null) {
+        if (mFooterView == null || mController == null) {
             return;
         }
         // TODO: move this logic to controller, which will invoke updateFooterView directly
@@ -2733,7 +2739,7 @@
      * @param listener callback for notification removed
      */
     public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mOnNotificationRemovedListener = listener;
     }
 
@@ -2779,8 +2785,7 @@
         if (animationGenerated) {
             if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
                 logAddTransientChild(child, container);
-                container.addTransientView(child, 0);
-                child.setTransientContainer(container);
+                child.addToTransientContainer(container, 0);
             }
         } else {
             mSwipedOutViews.remove(child);
@@ -2870,7 +2875,8 @@
      * Generate a remove animation for a child view.
      *
      * @param child The view to generate the remove animation for.
-     * @return Whether an animation was generated.
+     * @return Whether a new animation was generated or an existing animation was detected by this
+     * method. We need this to determine if a transient view is needed.
      */
     boolean generateRemoveAnimation(ExpandableView child) {
         String key = "";
@@ -2887,10 +2893,23 @@
             mAddedHeadsUpChildren.remove(child);
             return false;
         }
-        if (isClickedHeadsUp(child)) {
-            // An animation is already running, add it transiently
-            mClearTransientViewsWhenFinished.add(child);
-            return true;
+        if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) {
+            // Skip adding animation for clicked heads up notifications when the
+            // Shade is closed, because the animation event is generated in
+            // generateHeadsUpAnimationEvents. Only report that an animation was
+            // actually generated (thus requesting the transient view be added)
+            // if a removal animation is in progress.
+            if (!isExpanded() && isClickedHeadsUp(child)) {
+                // An animation is already running, add it transiently
+                mClearTransientViewsWhenFinished.add(child);
+                return child.inRemovalAnimation();
+            }
+        } else {
+            if (isClickedHeadsUp(child)) {
+                // An animation is already running, add it transiently
+                mClearTransientViewsWhenFinished.add(child);
+                return true;
+            }
         }
         if (mDebugRemoveAnimation) {
             Log.d(TAG, "generateRemove " + key
@@ -3129,10 +3148,6 @@
         requestChildrenUpdate();
     }
 
-    public boolean containsView(View v) {
-        return v.getParent() == this;
-    }
-
     public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
         // Modify the clipping for launching notifications
         mLaunchAnimationParams = params;
@@ -3395,11 +3410,6 @@
             mAnimationEvents.add(animEvent);
         }
         mChildrenChangingPositions.clear();
-        if (mGenerateChildOrderChangedEvent) {
-            mAnimationEvents.add(new AnimationEvent(null,
-                    AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
-            mGenerateChildOrderChangedEvent = false;
-        }
     }
 
     private void generateChildAdditionEvents() {
@@ -4541,7 +4551,8 @@
         return mFooterView != null && mFooterView.isHistoryShown();
     }
 
-    void setFooterView(@NonNull FooterView footerView) {
+    /** Bind the {@link FooterView} to the NSSL. */
+    public void setFooterView(@NonNull FooterView footerView) {
         int index = -1;
         if (mFooterView != null) {
             index = indexOfChild(mFooterView);
@@ -4552,6 +4563,16 @@
         if (mManageButtonClickListener != null) {
             mFooterView.setManageButtonClickListener(mManageButtonClickListener);
         }
+        mFooterView.setClearAllButtonClickListener(v -> {
+            if (mFooterClearAllListener != null) {
+                mFooterClearAllListener.onClearAll();
+            }
+            clearNotifications(ROWS_ALL, true /* closeShade */);
+            footerView.setSecondaryVisible(false /* visible */, true /* animate */);
+        });
+        if (FooterViewRefactor.isEnabled()) {
+            updateFooter();
+        }
     }
 
     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4614,7 +4635,9 @@
         mFooterView.setVisible(visible, animate);
         mFooterView.setSecondaryVisible(showDismissView, animate);
         mFooterView.showHistory(showHistory);
-        mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+        if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+        }
     }
 
     @VisibleForTesting
@@ -4749,14 +4772,6 @@
         info.setClassName(ScrollView.class.getName());
     }
 
-    public void generateChildOrderChangedEvent() {
-        if (mIsExpanded && mAnimationsEnabled) {
-            mGenerateChildOrderChangedEvent = true;
-            mNeedsAnimation = true;
-            requestChildrenUpdate();
-        }
-    }
-
     public int getContainerChildCount() {
         return getChildCount();
     }
@@ -4967,12 +4982,12 @@
 
     @Nullable
     public ExpandableView getShelf() {
-        if (!mShelfRefactor.expectEnabled()) return null;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null;
         return mShelf;
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
@@ -4986,7 +5001,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
@@ -5373,15 +5388,9 @@
 
     @VisibleForTesting
     protected void inflateFooterView() {
+        FooterViewRefactor.assertInLegacyMode();
         FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_notification_footer, this, false);
-        footerView.setClearAllButtonClickListener(v -> {
-            if (mFooterClearAllListener != null) {
-                mFooterClearAllListener.onClearAll();
-            }
-            clearNotifications(ROWS_ALL, true /* closeShade */);
-            footerView.setSecondaryVisible(false /* visible */, true /* animate */);
-        });
         setFooterView(footerView);
     }
 
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 9695cb1..21efd63 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
@@ -59,12 +59,13 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.common.ui.ConfigurationState;
 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.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -106,6 +107,8 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -114,13 +117,14 @@
 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.NotificationListInteractor;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -194,7 +198,7 @@
 
     private final GroupExpansionManager mGroupExpansionManager;
     private final NotifPipelineFlags mNotifPipelineFlags;
-    private final NotificationListInteractor mNotificationListInteractor;
+    private final SeenNotificationsInteractor mSeenNotificationsInteractor;
     private final KeyguardTransitionRepository mKeyguardTransitionRepo;
 
     private NotificationStackScrollLayout mView;
@@ -206,12 +210,14 @@
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private boolean mIsInTransitionToAod = false;
 
-    private final FeatureFlags mFeatureFlags;
-    private final ViewRefactorFlag mShelfRefactor;
+    private final FeatureFlagsClassic mFeatureFlags;
+    private final RefactorFlag mShelfRefactor;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final ActivityStarter mActivityStarter;
+    private final ConfigurationState mConfigurationState;
+    private final ShelfNotificationIconViewStore mShelfIconViewStore;
 
     private View mLongPressedView;
 
@@ -662,19 +668,21 @@
             UiEventLogger uiEventLogger,
             NotificationRemoteInputManager remoteInputManager,
             VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
-            NotificationListInteractor notificationListInteractor,
+            SeenNotificationsInteractor seenNotificationsInteractor,
             ShadeController shadeController,
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
             NotificationStackScrollLogger logger,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             NotificationIconAreaController notifIconAreaController,
-            FeatureFlags featureFlags,
+            FeatureFlagsClassic featureFlags,
             NotificationTargetsHelper notificationTargetsHelper,
             SecureSettings secureSettings,
             NotificationDismissibilityProvider dismissibilityProvider,
             ActivityStarter activityStarter,
-            SplitShadeStateController splitShadeStateController) {
+            SplitShadeStateController splitShadeStateController,
+            ConfigurationState configurationState,
+            ShelfNotificationIconViewStore shelfIconViewStore) {
         mView = view;
         mKeyguardTransitionRepo = keyguardTransitionRepo;
         mStackStateLogger = stackLogger;
@@ -715,15 +723,17 @@
         mUiEventLogger = uiEventLogger;
         mRemoteInputManager = remoteInputManager;
         mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
-        mNotificationListInteractor = notificationListInteractor;
+        mSeenNotificationsInteractor = seenNotificationsInteractor;
         mShadeController = shadeController;
         mNotifIconAreaController = notifIconAreaController;
         mFeatureFlags = featureFlags;
-        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
+        mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
         mActivityStarter = activityStarter;
+        mConfigurationState = configurationState;
+        mShelfIconViewStore = shelfIconViewStore;
         mView.passSplitShadeStateController(splitShadeStateController);
         updateResources();
         setUpView();
@@ -832,7 +842,10 @@
 
         mViewModel.ifPresent(
                 vm -> NotificationListViewBinder
-                        .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
+                        .bind(mView, vm, mConfigurationState, mConfigurationController,
+                                mFalsingManager,
+                                mNotifIconAreaController,
+                                mShelfIconViewStore));
 
         collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
                 this::onKeyguardTransitionChanged);
@@ -1431,7 +1444,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1644,12 +1657,12 @@
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mView.setShelf(shelf);
     }
 
     public int getShelfHeight() {
-        if (!mShelfRefactor.expectEnabled()) {
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) {
             return 0;
         }
         ExpandableView shelf = mView.getShelf();
@@ -1725,28 +1738,11 @@
         }
 
         @Override
-        public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
-            mView.generateAddAnimation(child, fromMoreCard);
-        }
-
-        @Override
-        public void generateChildOrderChangedEvent() {
-            mView.generateChildOrderChangedEvent();
-        }
-
-        @Override
         public int getContainerChildCount() {
             return mView.getContainerChildCount();
         }
 
         @Override
-        public void setNotificationActivityStarter(
-                NotificationActivityStarter notificationActivityStarter) {
-            NotificationStackScrollLayoutController.this
-                    .setNotificationActivityStarter(notificationActivityStarter);
-        }
-
-        @Override
         public int getTopClippingStartLocation() {
             return mView.getTopClippingStartLocation();
         }
@@ -1841,16 +1837,6 @@
         }
 
         @Override
-        public boolean containsView(View v) {
-            return mView.containsView(v);
-        }
-
-        @Override
-        public void setWillExpand(boolean willExpand) {
-            mView.setWillExpand(willExpand);
-        }
-
-        @Override
         public void dumpPipeline(@NonNull PipelineDumper d) {
             d.dump("NotificationStackScrollLayoutController.this",
                     NotificationStackScrollLayoutController.this);
@@ -2006,7 +1992,7 @@
         public void setNotifStats(@NonNull NotifStats notifStats) {
             mNotifStats = notifStats;
             mView.setHasFilteredOutSeenNotifications(
-                    mNotificationListInteractor.getHasFilteredOutSeenNotifications().getValue());
+                    mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue());
             updateFooter();
             updateShowEmptyShadeView();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 69453c6..e94258f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -346,21 +349,19 @@
             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
         boolean needsCustomAnimation = false;
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
-            final ExpandableView changingView = (ExpandableView) event.mChangingView;
+            final ExpandableView changingView = event.mChangingView;
             boolean loggable = false;
             boolean isHeadsUp = false;
-            boolean isGroupChild = false;
             String key = null;
             if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
                 loggable = true;
                 isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
-                isGroupChild = changingView.isChildInGroup();
                 key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
             }
             if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
 
-                // This item is added, initialize it's properties.
+                // This item is added, initialize its properties.
                 ExpandableViewState viewState = changingView.getViewState();
                 if (viewState == null || viewState.gone) {
                     // The position for this child was never generated, let's continue.
@@ -374,7 +375,11 @@
 
             } else if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
-                if (changingView.getVisibility() != View.VISIBLE) {
+                int changingViewVisibility = changingView.getVisibility();
+                if (loggable) {
+                    mLogger.processAnimationEventsRemoval(key, changingViewVisibility, isHeadsUp);
+                }
+                if (changingViewVisibility != View.VISIBLE) {
                     changingView.removeFromTransientContainer();
                     continue;
                 }
@@ -410,30 +415,40 @@
                     translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
 
                 }
-                Runnable postAnimation = changingView::removeFromTransientContainer;
+                Runnable postAnimation;
+                Runnable startAnimation;
                 if (loggable) {
                     String finalKey = key;
-                    if (isHeadsUp) {
-                        mLogger.logHUNViewDisappearingWithRemoveEvent(key);
-                        postAnimation = () -> {
-                            mLogger.disappearAnimationEnded(finalKey);
-                            changingView.removeFromTransientContainer();
-                        };
-                    } else if (isGroupChild) {
-                        mLogger.groupChildRemovalEventProcessed(key);
-                        postAnimation = () -> {
-                            mLogger.groupChildRemovalAnimationEnded(finalKey);
-                            changingView.removeFromTransientContainer();
-                        };
-                    }
+                    final boolean finalIsHeadsHp = isHeadsUp;
+                    startAnimation = () -> {
+                        mLogger.animationStart(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp);
+                        changingView.setInRemovalAnimation(true);
+                    };
+                    postAnimation = () -> {
+                        mLogger.animationEnd(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp);
+                        changingView.setInRemovalAnimation(false);
+                        changingView.removeFromTransientContainer();
+                    };
+                } else {
+                    startAnimation = ()-> {
+                        changingView.setInRemovalAnimation(true);
+                    };
+                    postAnimation = () -> {
+                        changingView.setInRemovalAnimation(false);
+                        changingView.removeFromTransientContainer();
+                    };
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        postAnimation, getGlobalAnimationFinishedListener());
+                        startAnimation, postAnimation, getGlobalAnimationFinishedListener());
                 needsCustomAnimation = true;
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
-                if (mHostLayout.isFullySwipedOut(changingView)) {
+                boolean isFullySwipedOut = mHostLayout.isFullySwipedOut(changingView);
+                if (loggable) {
+                    mLogger.processAnimationEventsRemoveSwipeOut(key, isFullySwipedOut, isHeadsUp);
+                }
+                if (isFullySwipedOut) {
                     changingView.removeFromTransientContainer();
                 }
             } else if (event.animationType == NotificationStackScrollLayout
@@ -442,7 +457,7 @@
                 row.prepareExpansionChanged();
             } else if (event.animationType == NotificationStackScrollLayout
                     .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
-                // This item is added, initialize it's properties.
+                // This item is added, initialize its properties.
                 ExpandableViewState viewState = changingView.getViewState();
                 mTmpState.copyFrom(viewState);
                 if (event.headsUpFromBottom) {
@@ -464,22 +479,23 @@
                 }
 
                 mTmpState.applyToView(changingView);
-            } else if (event.animationType == NotificationStackScrollLayout
-                            .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
-                    event.animationType == NotificationStackScrollLayout
-                            .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+                    || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
                 mHeadsUpDisappearChildren.add(changingView);
                 Runnable endRunnable = null;
                 if (changingView.getParent() == null) {
-                    // This notification was actually removed, so we need to add it transiently
+                    // This notification was actually removed, so we need to add it
+                    // transiently
                     mHostLayout.addTransientView(changingView, 0);
                     changingView.setTransientContainer(mHostLayout);
                     mTmpState.initFrom(changingView);
                     endRunnable = changingView::removeFromTransientContainer;
                 }
+
                 boolean needsAnimation = true;
                 if (changingView instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
+                    ExpandableNotificationRow row =
+                            (ExpandableNotificationRow) changingView;
                     if (row.isDismissed()) {
                         needsAnimation = false;
                     }
@@ -488,21 +504,43 @@
                     // We need to add the global animation listener, since once no animations are
                     // running anymore, the panel will instantly hide itself. We need to wait until
                     // the animation is fully finished for this though.
-                    Runnable postAnimation = endRunnable;
+                    final Runnable tmpEndRunnable = endRunnable;
+                    Runnable postAnimation;
+                    Runnable startAnimation;
                     if (loggable) {
-                        mLogger.logHUNViewDisappearing(key);
-
-                        Runnable finalEndRunnable = endRunnable;
                         String finalKey1 = key;
+                        final boolean finalIsHeadsUp = isHeadsUp;
+                        final String type =
+                                event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+                                        ? "ANIMATION_TYPE_HEADS_UP_DISAPPEAR"
+                                        : "ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK";
+                        startAnimation = () -> {
+                            mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(true);
+                        };
                         postAnimation = () -> {
-                            mLogger.disappearAnimationEnded(finalKey1);
-                            if (finalEndRunnable != null) finalEndRunnable.run();
+                            mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+                        };
+                    } else {
+                        postAnimation = () -> {
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+                        };
+                        startAnimation = () -> {
+                            changingView.setInRemovalAnimation(true);
                         };
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */,
-                            postAnimation, getGlobalAnimationFinishedListener());
+                            startAnimation, postAnimation,
+                            getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
                     endRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 0b2c486..d635f89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -5,74 +5,104 @@
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.visibilityString
 import javax.inject.Inject
 
-class StackStateLogger @Inject constructor(
+class StackStateLogger
+@Inject
+constructor(
     @NotificationHeadsUpLog private val buffer: LogBuffer,
     @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
 ) {
-    fun logHUNViewDisappearing(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up view disappearing $str1 "
-        })
-    }
 
     fun logHUNViewAppearing(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up notification view appearing $str1 "
-        })
-    }
-
-    fun logHUNViewDisappearingWithRemoveEvent(key: String) {
-        buffer.log(TAG, LogLevel.ERROR, {
-            str1 = logKey(key)
-        }, {
-            "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
-        })
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = logKey(key) },
+            { "Heads up notification view appearing $str1 " }
+        )
     }
 
     fun logHUNViewAppearingWithAddEvent(key: String) {
-        buffer.log(TAG, LogLevel.ERROR, {
-            str1 = logKey(key)
-        }, {
-            "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
-        })
-    }
-
-    fun disappearAnimationEnded(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up notification disappear animation ended $str1 "
-        })
+        buffer.log(
+            TAG,
+            LogLevel.ERROR,
+            { str1 = logKey(key) },
+            { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" }
+        )
     }
 
     fun appearAnimationEnded(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up notification appear animation ended $str1 "
-        })
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = logKey(key) },
+            { "Heads up notification appear animation ended $str1 " }
+        )
     }
 
-    fun groupChildRemovalEventProcessed(key: String) {
-        notificationRenderBuffer.log(TAG, LogLevel.DEBUG, {
-            str1 = logKey(key)
-        }, {
-            "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE"
-        })
+    fun processAnimationEventsRemoval(key: String, visibility: Int, isHeadsUp: Boolean) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                int1 = visibility
+                bool1 = isHeadsUp
+            },
+            {
+                "ProcessAnimationEvents ANIMATION_TYPE_REMOVE for: $str1, " +
+                    "changingViewVisibility: ${visibilityString(int1)}, isHeadsUp: $bool1"
+            }
+        )
     }
-    fun groupChildRemovalAnimationEnded(key: String) {
-        notificationRenderBuffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Group child notification removal animation ended $str1 "
-        })
+
+    fun processAnimationEventsRemoveSwipeOut(
+        key: String,
+        isFullySwipedOut: Boolean,
+        isHeadsUp: Boolean
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                bool1 = isFullySwipedOut
+                bool2 = isHeadsUp
+            },
+            {
+                "ProcessAnimationEvents ANIMATION_TYPE_REMOVE_SWIPED_OUT for: $str1, " +
+                    "isFullySwipedOut: $bool1, isHeadsUp: $bool2"
+            }
+        )
+    }
+
+    fun animationStart(key: String?, animationType: String, isHeadsUp: Boolean) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                str2 = animationType
+                bool1 = isHeadsUp
+            },
+            { "Animation Start, type: $str2, notif key: $str1, isHeadsUp: $bool1" }
+        )
+    }
+
+    fun animationEnd(key: String, animationType: String, isHeadsUp: Boolean) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                str2 = animationType
+                bool1 = isHeadsUp
+            },
+            { "Animation End, type: $str2, notif key: $str1, isHeadsUp: $bool1" }
+        )
     }
 }
 
-private const val TAG = "StackScroll"
\ No newline at end of file
+private const val TAG = "StackScroll"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
deleted file mode 100644
index f6ed8c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Repository for information about the current notification list. */
-@SysUISingleton
-class NotificationListRepository @Inject constructor() {
-    private val _hasFilteredOutSeenNotifications = MutableStateFlow(false)
-    val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
-        _hasFilteredOutSeenNotifications.asStateFlow()
-
-    fun setHasFilteredOutSeenNotifications(value: Boolean) {
-        _hasFilteredOutSeenNotifications.value = value
-    }
-}
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 dee3973..95b467f 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
@@ -17,14 +17,21 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
 import android.view.LayoutInflater
-import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.reinflateAndBindLatest
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationShelf
+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.ShelfNotificationIconViewStore
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.traceSection
 
 /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
 object NotificationListViewBinder {
@@ -32,9 +39,11 @@
     fun bind(
         view: NotificationStackScrollLayout,
         viewModel: NotificationListViewModel,
+        configuration: ConfigurationState,
+        configurationController: ConfigurationController,
         falsingManager: FalsingManager,
-        featureFlags: FeatureFlags,
         iconAreaController: NotificationIconAreaController,
+        shelfIconViewStore: ShelfNotificationIconViewStore,
     ) {
         val shelf =
             LayoutInflater.from(view.context)
@@ -42,10 +51,27 @@
         NotificationShelfViewBinder.bind(
             shelf,
             viewModel.shelf,
+            configuration,
+            configurationController,
             falsingManager,
-            featureFlags,
-            iconAreaController
+            iconAreaController,
+            shelfIconViewStore,
         )
         view.setShelf(shelf)
+
+        viewModel.footer.ifPresent { footerViewModel ->
+            // The footer needs to be re-inflated every time the theme or the font size changes.
+            view.repeatWhenAttached {
+                configuration.reinflateAndBindLatest(
+                    R.layout.status_bar_notification_footer,
+                    view,
+                    attachToRoot = false,
+                ) { footerView: FooterView ->
+                    traceSection("bind FooterView") {
+                        FooterViewBinder.bind(footerView, footerViewModel)
+                    }
+                }
+            }
+        }
     }
 }
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 11f68e0..261371d 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
@@ -17,8 +17,9 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import dagger.Module
 import dagger.Provides
@@ -28,6 +29,7 @@
 /** ViewModel for the list of notifications. */
 class NotificationListViewModel(
     val shelf: NotificationShelfViewModel,
+    val footer: Optional<FooterViewModel>,
 )
 
 @Module
@@ -36,12 +38,33 @@
     @Provides
     @SysUISingleton
     fun maybeProvideViewModel(
-        featureFlags: FeatureFlags,
+        featureFlags: FeatureFlagsClassic,
         shelfViewModel: Provider<NotificationShelfViewModel>,
-    ): Optional<NotificationListViewModel> =
-        if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-            Optional.of(NotificationListViewModel(shelfViewModel.get()))
+        footerViewModel: Provider<FooterViewModel>,
+    ): Optional<NotificationListViewModel> {
+        return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+                Optional.of(
+                    NotificationListViewModel(
+                        shelfViewModel.get(),
+                        Optional.of(footerViewModel.get())
+                    )
+                )
+            } else {
+                Optional.of(NotificationListViewModel(shelfViewModel.get(), Optional.empty()))
+            }
         } else {
+            if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+                throw IllegalStateException(
+                    "The com.android.systemui.notifications_footer_view_refactor flag requires " +
+                        "the notification_shelf_refactor flag to be enabled. First disable the " +
+                        "footer flag using `adb shell device_config put systemui " +
+                        "com.android.systemui.notifications_footer_view_refactor false`, then " +
+                        "enable the notification_shelf_refactor flag in Flag Flipper. " +
+                        "Afterwards, you can try re-enabling the footer refactor flag via adb."
+                )
+            }
             Optional.empty()
         }
+    }
 }
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 f750fed..1229cb9 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
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** View-model for the shared notification container, used by both the shade and keyguard spaces */
 class SharedNotificationContainerViewModel
@@ -45,8 +46,8 @@
 ) {
     private val statesForConstrainedNotifications =
         setOf(
-            KeyguardState.LOCKSCREEN,
             KeyguardState.AOD,
+            KeyguardState.LOCKSCREEN,
             KeyguardState.DOZING,
             KeyguardState.ALTERNATE_BOUNCER,
             KeyguardState.PRIMARY_BOUNCER
@@ -68,8 +69,17 @@
 
     /** If the user is visually on one of the unoccluded lockscreen states. */
     val isOnLockscreen: Flow<Boolean> =
-        keyguardTransitionInteractor.finishedKeyguardState
-            .map { statesForConstrainedNotifications.contains(it) }
+        combine(
+                keyguardTransitionInteractor.finishedKeyguardState.map {
+                    statesForConstrainedNotifications.contains(it)
+                },
+                keyguardTransitionInteractor
+                    .transitionValue(KeyguardState.LOCKSCREEN)
+                    .onStart { emit(0f) }
+                    .map { it > 0 }
+            ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
+                constrainedNotificationState || transitioningToOrFromLockscreen
+            }
             .distinctUntilChanged()
 
     /** Are we purely on the keyguard without the shade/qs? */
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 07d3a1c..2d125462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,7 +30,6 @@
 import android.view.WindowManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
 import com.android.systemui.animation.DelegateLaunchAnimatorController
@@ -43,6 +42,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
@@ -134,6 +134,19 @@
         )
     }
 
+    override fun startPendingIntentMaybeDismissingKeyguard(
+        intent: PendingIntent,
+        intentSentUiThreadCallback: Runnable?,
+        animationController: ActivityLaunchAnimator.Controller?
+    ) {
+        activityStarterInternal.startPendingIntentDismissingKeyguard(
+            intent = intent,
+            intentSentUiThreadCallback = intentSentUiThreadCallback,
+            animationController = animationController,
+            showOverLockscreen = true,
+        )
+    }
+
     /**
      * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
      *   this.
@@ -454,7 +467,7 @@
                     !willLaunchResolverActivity &&
                     shouldAnimateLaunch(isActivityIntent = true)
             val animController =
-                wrapAnimationController(
+                wrapAnimationControllerForShadeOrStatusBar(
                     animationController = animationController,
                     dismissShade = dismissShade,
                     isLaunchForActivity = true,
@@ -547,12 +560,18 @@
             )
         }
 
-        /** Starts a pending intent after dismissing keyguard. */
+        /**
+         * Starts a pending intent after dismissing keyguard.
+         *
+         * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in
+         * the main thread).
+         */
         fun startPendingIntentDismissingKeyguard(
             intent: PendingIntent,
             intentSentUiThreadCallback: Runnable? = null,
             associatedView: View? = null,
             animationController: ActivityLaunchAnimator.Controller? = null,
+            showOverLockscreen: Boolean = false,
         ) {
             val animationController =
                 if (associatedView is ExpandableNotificationRow) {
@@ -566,79 +585,103 @@
                         lockScreenUserManager.currentUserId,
                     ))
 
+            val actuallyShowOverLockscreen =
+                showOverLockscreen &&
+                    intent.isActivity &&
+                    activityIntentHelper.wouldPendingShowOverLockscreen(
+                        intent,
+                        lockScreenUserManager.currentUserId
+                    )
+
             val animate =
                 !willLaunchResolverActivity &&
                     animationController != null &&
-                    shouldAnimateLaunch(intent.isActivity)
+                    shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+            // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+            // that the shade is collapsed after the animation (or when it is cancelled,
+            // aborted, etc).
+            val statusBarController =
+                wrapAnimationControllerForShadeOrStatusBar(
+                    animationController = animationController,
+                    dismissShade = true,
+                    isLaunchForActivity = intent.isActivity,
+                )
+            val controller =
+                if (actuallyShowOverLockscreen) {
+                    wrapAnimationControllerForLockscreen(statusBarController)
+                } else {
+                    statusBarController
+                }
 
             // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
             // run the animation on the keyguard). The animation will take care of (instantly)
             // collapsing the shade and hiding the keyguard once it is done.
             val collapse = !animate
-            executeRunnableDismissingKeyguard(
-                runnable = {
-                    try {
-                        // We wrap animationCallback with a StatusBarLaunchAnimatorController so
-                        // that the shade is collapsed after the animation (or when it is cancelled,
-                        // aborted, etc).
-                        val controller: ActivityLaunchAnimator.Controller? =
-                            wrapAnimationController(
-                                animationController = animationController,
-                                dismissShade = true,
-                                isLaunchForActivity = intent.isActivity,
-                            )
-                        activityLaunchAnimator.startPendingIntentWithAnimation(
-                            controller,
-                            animate,
-                            intent.creatorPackage,
-                            object : PendingIntentStarter {
-                                override fun startPendingIntent(
-                                    animationAdapter: RemoteAnimationAdapter?
-                                ): Int {
-                                    val options =
-                                        ActivityOptions(
-                                            CentralSurfaces.getActivityOptions(
-                                                displayId,
-                                                animationAdapter
-                                            )
+            val runnable = Runnable {
+                try {
+                    activityLaunchAnimator.startPendingIntentWithAnimation(
+                        controller,
+                        animate,
+                        intent.creatorPackage,
+                        actuallyShowOverLockscreen,
+                        object : PendingIntentStarter {
+                            override fun startPendingIntent(
+                                animationAdapter: RemoteAnimationAdapter?
+                            ): Int {
+                                val options =
+                                    ActivityOptions(
+                                        CentralSurfaces.getActivityOptions(
+                                            displayId,
+                                            animationAdapter
                                         )
-                                    // TODO b/221255671: restrict this to only be set for
-                                    // notifications
-                                    options.isEligibleForLegacyPermissionPrompt = true
-                                    options.setPendingIntentBackgroundActivityStartMode(
-                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
                                     )
-                                    return intent.sendAndReturnResult(
-                                        null,
-                                        0,
-                                        null,
-                                        null,
-                                        null,
-                                        null,
-                                        options.toBundle()
-                                    )
-                                }
-                            },
-                        )
-                    } catch (e: PendingIntent.CanceledException) {
-                        // the stack trace isn't very helpful here.
-                        // Just log the exception message.
-                        Log.w(TAG, "Sending intent failed: $e")
-                        if (!collapse) {
-                            // executeRunnableDismissingKeyguard did not collapse for us already.
-                            shadeControllerLazy.get().collapseOnMainThread()
-                        }
-                        // TODO: Dismiss Keyguard.
+                                // TODO b/221255671: restrict this to only be set for
+                                // notifications
+                                options.isEligibleForLegacyPermissionPrompt = true
+                                options.setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                                )
+                                return intent.sendAndReturnResult(
+                                    null,
+                                    0,
+                                    null,
+                                    null,
+                                    null,
+                                    null,
+                                    options.toBundle()
+                                )
+                            }
+                        },
+                    )
+                } catch (e: PendingIntent.CanceledException) {
+                    // the stack trace isn't very helpful here.
+                    // Just log the exception message.
+                    Log.w(TAG, "Sending intent failed: $e")
+                    if (!collapse) {
+                        // executeRunnableDismissingKeyguard did not collapse for us already.
+                        shadeControllerLazy.get().collapseOnMainThread()
                     }
-                    if (intent.isActivity) {
-                        assistManagerLazy.get().hideAssist()
-                    }
-                    intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
-                },
-                afterKeyguardGone = willLaunchResolverActivity,
-                dismissShade = collapse,
-                willAnimateOnKeyguard = animate,
-            )
+                    // TODO: Dismiss Keyguard.
+                }
+                if (intent.isActivity) {
+                    assistManagerLazy.get().hideAssist()
+                }
+                intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
+            }
+
+            if (!actuallyShowOverLockscreen) {
+                postOnUiThread(delay = 0) {
+                    executeRunnableDismissingKeyguard(
+                        runnable = runnable,
+                        afterKeyguardGone = willLaunchResolverActivity,
+                        dismissShade = collapse,
+                        willAnimateOnKeyguard = animate,
+                    )
+                }
+            } else {
+                postOnUiThread(delay = 0, runnable)
+            }
         }
 
         /** Starts an Activity. */
@@ -678,71 +721,12 @@
                 // Wrap the animation controller to dismiss the shade and set
                 // mIsLaunchingActivityOverLockscreen during the animation.
                 val delegate =
-                    wrapAnimationController(
+                    wrapAnimationControllerForShadeOrStatusBar(
                         animationController = animationController,
                         dismissShade = dismissShade,
                         isLaunchForActivity = true,
                     )
-                delegate?.let {
-                    controller =
-                        object : DelegateLaunchAnimatorController(delegate) {
-                            override fun onIntentStarted(willAnimate: Boolean) {
-                                delegate?.onIntentStarted(willAnimate)
-                                if (willAnimate) {
-                                    centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
-                                }
-                            }
-
-                            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                                super.onLaunchAnimationStart(isExpandingFullyAbove)
-
-                                // Double check that the keyguard is still showing and not going
-                                // away, but if so set the keyguard occluded. Typically, WM will let
-                                // KeyguardViewMediator know directly, but we're overriding that to
-                                // play the custom launch animation, so we need to take care of that
-                                // here. The unocclude animation is not overridden, so WM will call
-                                // KeyguardViewMediator's unocclude animation runner when the
-                                // activity is exited.
-                                if (
-                                    keyguardStateController.isShowing &&
-                                        !keyguardStateController.isKeyguardGoingAway
-                                ) {
-                                    Log.d(TAG, "Setting occluded = true in #startActivity.")
-                                    keyguardViewMediatorLazy
-                                        .get()
-                                        .setOccluded(true /* isOccluded */, true /* animate */)
-                                }
-                            }
-
-                            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                                // Set mIsLaunchingActivityOverLockscreen to false before actually
-                                // finishing the animation so that we can assume that
-                                // mIsLaunchingActivityOverLockscreen being true means that we will
-                                // collapse the shade (or at least run the post collapse runnables)
-                                // later on.
-                                centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
-                                delegate?.onLaunchAnimationEnd(isExpandingFullyAbove)
-                            }
-
-                            override fun onLaunchAnimationCancelled(
-                                newKeyguardOccludedState: Boolean?
-                            ) {
-                                if (newKeyguardOccludedState != null) {
-                                    keyguardViewMediatorLazy
-                                        .get()
-                                        .setOccluded(newKeyguardOccludedState, false /* animate */)
-                                }
-
-                                // Set mIsLaunchingActivityOverLockscreen to false before actually
-                                // finishing the animation so that we can assume that
-                                // mIsLaunchingActivityOverLockscreen being true means that we will
-                                // collapse the shade (or at least run the // post collapse
-                                // runnables) later on.
-                                centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
-                                delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
-                            }
-                        }
-                }
+                controller = wrapAnimationControllerForLockscreen(delegate)
             } else if (dismissShade) {
                 // The animation will take care of dismissing the shade at the end of the animation.
                 // If we don't animate, collapse it directly.
@@ -874,7 +858,7 @@
          *   window.
          * @param isLaunchForActivity whether the launch is for an activity.
          */
-        private fun wrapAnimationController(
+        private fun wrapAnimationControllerForShadeOrStatusBar(
             animationController: ActivityLaunchAnimator.Controller?,
             dismissShade: Boolean,
             isLaunchForActivity: Boolean,
@@ -909,6 +893,72 @@
             return animationController
         }
 
+        /**
+         * Wraps an animation controller so that if an activity would be launched on top of the
+         * lockscreen, the correct flags are set for it to be occluded.
+         */
+        private fun wrapAnimationControllerForLockscreen(
+            animationController: ActivityLaunchAnimator.Controller?
+        ): ActivityLaunchAnimator.Controller? {
+            return animationController?.let {
+                object : DelegateLaunchAnimatorController(it) {
+                    override fun onIntentStarted(willAnimate: Boolean) {
+                        delegate.onIntentStarted(willAnimate)
+                        if (willAnimate) {
+                            centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
+                        }
+                    }
+
+                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                        super.onLaunchAnimationStart(isExpandingFullyAbove)
+
+                        // Double check that the keyguard is still showing and not going
+                        // away, but if so set the keyguard occluded. Typically, WM will let
+                        // KeyguardViewMediator know directly, but we're overriding that to
+                        // play the custom launch animation, so we need to take care of that
+                        // here. The unocclude animation is not overridden, so WM will call
+                        // KeyguardViewMediator's unocclude animation runner when the
+                        // activity is exited.
+                        if (
+                            keyguardStateController.isShowing &&
+                                !keyguardStateController.isKeyguardGoingAway
+                        ) {
+                            Log.d(TAG, "Setting occluded = true in #startActivity.")
+                            keyguardViewMediatorLazy
+                                .get()
+                                .setOccluded(true /* isOccluded */, true /* animate */)
+                        }
+                    }
+
+                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        // Set mIsLaunchingActivityOverLockscreen to false before actually
+                        // finishing the animation so that we can assume that
+                        // mIsLaunchingActivityOverLockscreen being true means that we will
+                        // collapse the shade (or at least run the post collapse runnables)
+                        // later on.
+                        centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                    }
+
+                    override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+                        if (newKeyguardOccludedState != null) {
+                            keyguardViewMediatorLazy
+                                .get()
+                                .setOccluded(newKeyguardOccludedState, false /* animate */)
+                        }
+
+                        // Set mIsLaunchingActivityOverLockscreen to false before actually
+                        // finishing the animation so that we can assume that
+                        // mIsLaunchingActivityOverLockscreen being true means that we will
+                        // collapse the shade (or at least run the // post collapse
+                        // runnables) later on.
+                        centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+                        delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
+                    }
+                }
+            }
+        }
+
         /** Retrieves the current user handle to start the Activity. */
         private fun getActivityUserHandle(intent: Intent): UserHandle {
             val packages: Array<String> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 697d297..3877bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -28,15 +28,15 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -461,8 +461,8 @@
     };
 
     @VisibleForTesting
-    protected SettingObserver getSecureSettingForKey(String key) {
-        for (SettingObserver s : mAutoAddSettingList) {
+    protected UserSettingObserver getSecureSettingForKey(String key) {
+        for (UserSettingObserver s : mAutoAddSettingList) {
             if (Objects.equals(key, s.getKey())) {
                 return s;
             }
@@ -476,7 +476,7 @@
      * When the setting changes to a value different from 0, if the tile has not been auto added
      * before, it will be added and the listener will be stopped.
      */
-    private class AutoAddSetting extends SettingObserver {
+    private class AutoAddSetting extends UserSettingObserver {
         private final String mSpec;
 
         AutoAddSetting(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 2809cad..cbe9d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
 
 import android.annotation.IntDef;
 import android.content.res.Resources;
@@ -30,7 +28,6 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.Trace;
-import android.view.HapticFeedbackConstants;
 
 import androidx.annotation.Nullable;
 
@@ -47,22 +44,27 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.time.SystemClock;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -73,12 +75,14 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Controller which coordinates all the biometric unlocking actions with the UI.
  */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
-    private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L;
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -172,9 +176,12 @@
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final LatencyTracker mLatencyTracker;
     private final VibratorHelper mVibratorHelper;
+    private final BiometricUnlockInteractor mBiometricUnlockInteractor;
     private final BiometricUnlockLogger mLogger;
     private final SystemClock mSystemClock;
     private final boolean mOrderUnlockAndWake;
+    private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
+    private final DeviceEntryHapticsInteractor mHapticsInteractor;
 
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
@@ -284,7 +291,10 @@
             ScreenOffAnimationController screenOffAnimationController,
             VibratorHelper vibrator,
             SystemClock systemClock,
-            FeatureFlags featureFlags
+            FeatureFlags featureFlags,
+            DeviceEntryHapticsInteractor hapticsInteractor,
+            Lazy<SelectedUserInteractor> selectedUserInteractor,
+            BiometricUnlockInteractor biometricUnlockInteractor
     ) {
         mPowerManager = powerManager;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -293,6 +303,7 @@
         mLatencyTracker = latencyTracker;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+        mBiometricUnlockInteractor = biometricUnlockInteractor;
 
         mNotificationShadeWindowController = notificationShadeWindowController;
         mDozeScrimController = dozeScrimController;
@@ -314,6 +325,8 @@
         mFeatureFlags = featureFlags;
         mOrderUnlockAndWake = resources.getBoolean(
                 com.android.internal.R.bool.config_orderUnlockAndWake);
+        mHapticsInteractor = hapticsInteractor;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         dumpManager.registerDumpable(this);
     }
@@ -434,7 +447,7 @@
         if (mode == MODE_WAKE_AND_UNLOCK
                 || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
                 || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
-            vibrateSuccess(biometricSourceType);
+            mHapticsInteractor.vibrateSuccess();
             onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
         }
         startWakeAndUnlock(mode);
@@ -498,8 +511,6 @@
             case MODE_WAKE_AND_UNLOCK:
                 if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
-                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */,
-                            true /* allowEnterAnimation */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
                 } else {
@@ -523,6 +534,7 @@
         for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
             listener.onModeChanged(mode);
         }
+        mBiometricUnlockInteractor.setBiometricUnlockState(mode);
     }
 
     private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) {
@@ -535,7 +547,8 @@
         return mPendingAuthenticated != null
                 && mUpdateMonitor
                     .isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric)
-                && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
+                && mPendingAuthenticated.userId
+                    == mSelectedUserInteractor.get().getSelectedUserId();
     }
 
     public @WakeAndUnlockMode int getMode() {
@@ -599,11 +612,11 @@
             // if unlocking isn't allowed, log more information about why unlocking may not
             // have been allowed
             final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
-                    KeyguardUpdateMonitor.getCurrentUser());
+                    mSelectedUserInteractor.get().getSelectedUserId());
             final boolean nonStrongBiometricAllowed =
                     mUpdateMonitor.getStrongAuthTracker()
                             .isNonStrongBiometricAllowedAfterIdleTimeout(
-                                    KeyguardUpdateMonitor.getCurrentUser());
+                                    mSelectedUserInteractor.get().getSelectedUserId());
 
             mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric,
                     strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing);
@@ -669,11 +682,11 @@
             // if unlocking isn't allowed, log more information about why unlocking may not
             // have been allowed
             final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
-                    KeyguardUpdateMonitor.getCurrentUser());
+                    mSelectedUserInteractor.get().getSelectedUserId());
             final boolean nonStrongBiometricAllowed =
                     mUpdateMonitor.getStrongAuthTracker()
                             .isNonStrongBiometricAllowedAfterIdleTimeout(
-                                    KeyguardUpdateMonitor.getCurrentUser());
+                                    mSelectedUserInteractor.get().getSelectedUserId());
 
             mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed(
                     strongBiometric, strongAuthFlags, nonStrongBiometricAllowed,
@@ -720,10 +733,10 @@
 
         // Suppress all face auth errors if fingerprint can be used to authenticate
         if ((biometricSourceType == BiometricSourceType.FACE
-                && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser()))
+                && !mUpdateMonitor.isUnlockWithFingerprintPossible(
+                    mSelectedUserInteractor.get().getSelectedUserId()))
                 || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
-            vibrateError(biometricSourceType);
+            mHapticsInteractor.vibrateError();
         }
 
         cleanup();
@@ -750,45 +763,6 @@
         cleanup();
     }
 
-    // these haptics are for device-entry only
-    private void vibrateSuccess(BiometricSourceType type) {
-        if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
-                && lastWakeupFromPowerButtonWithinHapticThreshold()) {
-            mLogger.d("Skip auth success haptic. Power button was recently pressed.");
-            return;
-        }
-        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            mVibratorHelper.performHapticFeedback(
-                    mKeyguardViewController.getViewRootImpl().getView(),
-                    HapticFeedbackConstants.CONFIRM
-            );
-        } else {
-            mVibratorHelper.vibrateAuthSuccess(
-                    getClass().getSimpleName() + ", type =" + type + "device-entry::success");
-        }
-    }
-
-    private boolean lastWakeupFromPowerButtonWithinHapticThreshold() {
-        final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason()
-                == PowerManager.WAKE_REASON_POWER_BUTTON;
-        return lastWakeupFromPowerButton
-                && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME
-                && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime()
-                < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS;
-    }
-
-    private void vibrateError(BiometricSourceType type) {
-        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            mVibratorHelper.performHapticFeedback(
-                    mKeyguardViewController.getViewRootImpl().getView(),
-                    HapticFeedbackConstants.REJECT
-            );
-        } else {
-            mVibratorHelper.vibrateAuthError(
-                    getClass().getSimpleName() + ", type =" + type + "device-entry::error");
-        }
-    }
-
     private void cleanup() {
         releaseBiometricWakeLock();
     }
@@ -818,6 +792,7 @@
         for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
             listener.onResetMode();
         }
+        mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE);
         mNumConsecutiveFpFailures = 0;
         mLastFpFailureUptimeMillis = 0;
     }
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 f380ce5..897bb42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -76,7 +76,6 @@
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
 import android.view.Display;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
@@ -176,7 +175,6 @@
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.statusbar.AutoHideUiElement;
-import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CircleReveal;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -204,11 +202,11 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -237,8 +235,6 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
-import dagger.Lazy;
-
 import dalvik.annotation.optimization.NeverCompile;
 
 import java.io.PrintWriter;
@@ -252,6 +248,8 @@
 import javax.inject.Named;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 /**
  * A class handling initialization and coordination between some of the key central surfaces in
  * System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -298,7 +296,6 @@
     private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
     private float mTransitionToFullShadeProgress = 0f;
     private final NotificationListContainer mNotifListContainer;
-    private final NotificationExpansionRepository mNotificationExpansionRepository;
     private boolean mIsShortcutListSearchEnabled;
 
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -312,8 +309,8 @@
             };
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
-        updateBubblesVisibility();
         mStatusBarWindowState = state;
+        updateBubblesVisibility();
     }
 
     @Override
@@ -376,9 +373,6 @@
     private boolean mBrightnessMirrorVisible;
     private BiometricUnlockController mBiometricUnlockController;
     private final LightBarController mLightBarController;
-    private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
-    @Nullable
-    protected LockscreenWallpaper mLockscreenWallpaper;
     private final AutoHideController mAutoHideController;
 
     private final Point mCurrentDisplaySize = new Point();
@@ -655,10 +649,8 @@
             Lazy<NotificationPresenter> notificationPresenterLazy,
             Lazy<NotificationActivityStarter> notificationActivityStarterLazy,
             NotificationLaunchAnimatorControllerProvider notifLaunchAnimatorControllerProvider,
-            NotificationExpansionRepository notificationExpansionRepository,
             DozeParameters dozeParameters,
             ScrimController scrimController,
-            Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
@@ -765,12 +757,10 @@
         mPresenterLazy = notificationPresenterLazy;
         mNotificationActivityStarterLazy = notificationActivityStarterLazy;
         mNotificationAnimationProvider = notifLaunchAnimatorControllerProvider;
-        mNotificationExpansionRepository = notificationExpansionRepository;
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
-        mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
         mAuthRippleController = authRippleController;
@@ -820,8 +810,6 @@
                 mShadeExpansionStateManager.addExpansionListener(shadeExpansionListener);
         shadeExpansionListener.onPanelExpansionChanged(currentState);
 
-        mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
-
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
         mActivityLaunchAnimator = activityLaunchAnimator;
 
@@ -1088,6 +1076,7 @@
      * @deprecated use {@link
      * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
      */    @VisibleForTesting
+    @Deprecated
     void initShadeVisibilityListener() {
         mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
             @Override
@@ -1197,10 +1186,6 @@
 
         createNavigationBar(result);
 
-        if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
-            mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
-        }
-
         mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
                 R.id.ambient_indication_container);
 
@@ -1267,24 +1252,6 @@
                 mNotificationShelfController,
                 mHeadsUpManager);
 
-        BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            mMediaManager.setup(null, null, null, mScrimController, null);
-        } else {
-            mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
-                    backdrop.findViewById(R.id.backdrop_back), mScrimController,
-                    mLockscreenWallpaper);
-        }
-        float maxWallpaperZoom = mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_wallpaperMaxScale);
-        mNotificationShadeDepthControllerLazy.get().addListener(depth -> {
-            float scale = MathUtils.lerp(maxWallpaperZoom, 1f, depth);
-            backdrop.setPivotX(backdrop.getWidth() / 2f);
-            backdrop.setPivotY(backdrop.getHeight() / 2f);
-            backdrop.setScaleX(scale);
-            backdrop.setScaleY(scale);
-        });
-
         // Set up the quick settings tile panel
         final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
         if (container != null) {
@@ -1356,14 +1323,6 @@
         // receive broadcasts
         registerBroadcastReceiver();
 
-        IntentFilter demoFilter = new IntentFilter();
-        if (DEBUG_MEDIA_FAKE_ARTWORK) {
-            demoFilter.addAction(ACTION_FAKE_ARTWORK);
-        }
-        mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
-                android.Manifest.permission.DUMP, null,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-
         // listen for USER_SETUP_COMPLETE setting (per-user)
         mDeviceProvisionedController.addCallback(mUserSetupObserver);
         mUserSetupObserver.onUserSetupChanged();
@@ -1437,20 +1396,6 @@
         }
     }
 
-    @VisibleForTesting
-    void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
-            if (DEBUG) {
-                Log.v(TAG, "clearing notification effects from Height");
-            }
-            clearNotificationEffects();
-        }
-
-        if (!isExpanded) {
-            mRemoteInputManager.onPanelCollapsed();
-        }
-    }
-
     @NonNull
     @Override
     public Lifecycle getLifecycle() {
@@ -1524,6 +1469,7 @@
         // regressions, we'll continue standing up the root view in CentralSurfaces.
         mNotificationShadeWindowController.fetchWindowRootView();
         getNotificationShadeWindowViewController().setupExpandedStatusBar();
+        getNotificationShadeWindowViewController().setupCommunalHubLayout();
         mShadeController.setNotificationShadeWindowViewController(
                 getNotificationShadeWindowViewController());
         mBackActionInteractor.setup(mQsController, mShadeSurface);
@@ -1581,7 +1527,6 @@
         mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
-        mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
         Trace.endSection();
     }
 
@@ -1725,7 +1670,8 @@
         StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue();
         mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
                 mode != StatusBarMode.LIGHTS_OUT
-                        && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT));
+                        && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT
+                        && mStatusBarWindowState != WINDOW_STATE_HIDDEN));
     }
 
     void checkBarMode(
@@ -1867,7 +1813,6 @@
     void updateDisplaySize() {
         mDisplay.getMetrics(mDisplayMetrics);
         mDisplay.getSize(mCurrentDisplaySize);
-        mMediaManager.onDisplayUpdated(mDisplay);
         if (DEBUG_GESTURES) {
             mGestureRec.tag("display",
                     String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
@@ -1941,19 +1886,6 @@
         }
     };
 
-    private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Log.v(TAG, "onReceive: " + intent);
-            String action = intent.getAction();
-            if (ACTION_FAKE_ARTWORK.equals(action)) {
-                if (DEBUG_MEDIA_FAKE_ARTWORK) {
-                    mPresenterLazy.get().updateMediaMetaData(true, true);
-                }
-            }
-        }
-    };
-
     /**
      * Reload some of our resources when the configuration changes.
      *
@@ -2136,7 +2068,6 @@
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mKeyguardStateController.setLaunchTransitionFadingAway(false);
-        mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
     /**
@@ -2160,7 +2091,6 @@
                 beforeFading.run();
             }
             updateScrimController();
-            mPresenterLazy.get().updateMediaMetaData(false, true);
             mShadeSurface.resetAlpha();
             mShadeSurface.fadeOut(
                     FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
@@ -2581,7 +2511,7 @@
                                     && mFingerprintManager.get() != null
                                     && mFingerprintManager.get().isPowerbuttonFps()
                                     && mKeyguardUpdateMonitor
-                                    .getCachedIsUnlockWithFingerprintPossible(
+                                    .isUnlockWithFingerprintPossible(
                                             mUserTracker.getUserId())
                                     && !touchToUnlockAnytime;
                     if (DEBUG_WAKEUP_DELAY) {
@@ -2602,7 +2532,10 @@
                         mShouldDelayWakeUpAnimation);
 
                 updateIsKeyguard();
+                // TODO(b/301913237): can't delay transition if config_displayBlanksAfterDoze=true,
+                // otherwise, the clock will flicker during LOCKSCREEN_TRANSITION_FROM_AOD
                 mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn()
+                        && !mDozeParameters.getDisplayNeedsBlanking()
                         && mFeatureFlags.isEnabled(
                                 Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD);
                 if (!mShouldDelayLockscreenTransitionFromAod) {
@@ -2687,7 +2620,7 @@
                 !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive());
 
         mShadeSurface.setTouchAndAnimationDisabled(disabled);
-        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+        if (!NotificationIconContainerRefactor.isEnabled()) {
             mNotificationIconAreaController.setAnimationsEnabled(!disabled);
         }
     }
@@ -3154,7 +3087,9 @@
             }
             // TODO: Bring these out of CentralSurfaces.
             mUserInfoControllerImpl.onDensityOrFontScaleChanged();
-            mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+            if (!NotificationIconContainerRefactor.isEnabled()) {
+                mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+            }
         }
 
         @Override
@@ -3172,7 +3107,9 @@
             if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
                 ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
             }
-            mNotificationIconAreaController.onThemeChanged();
+            if (!NotificationIconContainerRefactor.isEnabled()) {
+                mNotificationIconAreaController.onThemeChanged();
+            }
         }
 
         @Override
@@ -3216,8 +3153,6 @@
                     updateDozingState();
                     checkBarModes();
                     updateScrimController();
-                    mPresenterLazy.get()
-                            .updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
                     Trace.endSection();
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 1576aa2..6f992ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -47,6 +47,11 @@
     private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
 
     private int mIconTint = DEFAULT_ICON_TINT;
+    private int mContrastTint = DEFAULT_INVERSE_ICON_TINT;
+
+    private int mDarkModeContrastColor = DEFAULT_ICON_TINT;
+    private int mLightModeContrastColor = DEFAULT_INVERSE_ICON_TINT;
+
     private float mDarkIntensity;
     private int mDarkModeIconColorSingleTone;
     private int mLightModeIconColorSingleTone;
@@ -83,6 +88,7 @@
     public void addDarkReceiver(DarkReceiver receiver) {
         mReceivers.put(receiver, receiver);
         receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+        receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
     }
 
     public void addDarkReceiver(ImageView imageView) {
@@ -90,6 +96,7 @@
                 ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));
         mReceivers.put(imageView, receiver);
         receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+        receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
     }
 
     public void removeDarkReceiver(DarkReceiver object) {
@@ -102,6 +109,7 @@
 
     public void applyDark(DarkReceiver object) {
         mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+        mReceivers.get(object).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
     }
 
     /**
@@ -125,8 +133,13 @@
     @Override
     public void applyDarkIntensity(float darkIntensity) {
         mDarkIntensity = darkIntensity;
-        mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+        ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
+
+        mIconTint = (int) evaluator.evaluate(darkIntensity,
                 mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+        mContrastTint = (int) evaluator
+                .evaluate(darkIntensity, mLightModeContrastColor, mDarkModeContrastColor);
+
         applyIconTint();
     }
 
@@ -139,6 +152,7 @@
         mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));
         for (int i = 0; i < mReceivers.size(); i++) {
             mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+            mReceivers.valueAt(i).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
         }
     }
 
@@ -146,6 +160,16 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("DarkIconDispatcher: ");
         pw.println("  mIconTint: 0x" + Integer.toHexString(mIconTint));
+        pw.println("  mContrastTint: 0x" + Integer.toHexString(mContrastTint));
+
+        pw.println("  mDarkModeIconColorSingleTone: 0x"
+                + Integer.toHexString(mDarkModeIconColorSingleTone));
+        pw.println("  mLightModeIconColorSingleTone: 0x"
+                + Integer.toHexString(mLightModeIconColorSingleTone));
+
+        pw.println("  mDarkModeContrastColor: 0x" + Integer.toHexString(mDarkModeContrastColor));
+        pw.println("  mLightModeContrastColor: 0x" + Integer.toHexString(mLightModeContrastColor));
+
         pw.println("  mDarkIntensity: " + mDarkIntensity + "f");
         pw.println("  mTintAreas: " + mTintAreas);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index de9854a..5deb08a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -28,10 +28,10 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
@@ -54,6 +54,7 @@
     private ModernStatusBarWifiView mModernWifiView;
     private boolean mDemoMode;
     private int mColor;
+    private int mContrastColor;
 
     private final MobileIconsViewModel mMobileIconsViewModel;
     private final StatusBarLocation mLocation;
@@ -68,6 +69,7 @@
         mStatusIcons = statusIcons;
         mIconSize = iconSize;
         mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+        mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT;
         mMobileIconsViewModel = mobileIconsViewModel;
         mLocation = location;
 
@@ -89,15 +91,17 @@
         ((ViewGroup) getParent()).removeView(this);
     }
 
-    public void setColor(int color) {
+    /** Set the tint colors */
+    public void setColor(int color, int contrastColor) {
         mColor = color;
+        mContrastColor = contrastColor;
         updateColors();
     }
 
     private void updateColors() {
         for (int i = 0; i < getChildCount(); i++) {
             StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i);
-            child.setStaticDrawableColor(mColor);
+            child.setStaticDrawableColor(mColor, mContrastColor);
             child.setDecorColor(mColor);
         }
     }
@@ -223,7 +227,7 @@
         StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false);
         v.setTag(slot);
         v.set(icon);
-        v.setStaticDrawableColor(mColor);
+        v.setStaticDrawableColor(mColor, mContrastColor);
         v.setDecorColor(mColor);
         addView(v, 0, createLayoutParams());
     }
@@ -269,7 +273,7 @@
         }
 
         mModernWifiView = view;
-        mModernWifiView.setStaticDrawableColor(mColor);
+        mModernWifiView.setStaticDrawableColor(mColor, mContrastColor);
         addView(view, viewIndex, createLayoutParams());
     }
 
@@ -305,14 +309,20 @@
     }
 
     @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
+    public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {
+        setColor(tint, contrastTint);
 
         if (mModernWifiView != null) {
-            mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+            mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint);
         }
+
         for (ModernStatusBarMobileView view : mModernMobileViews) {
-            view.onDarkChanged(areas, darkIntensity, tint);
+            view.onDarkChangedWithContrast(areas, tint, contrastTint);
         }
     }
+
+    @Override
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        // not needed
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 7730f7d9..a11cbc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -37,14 +37,15 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -55,9 +56,7 @@
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
 import java.util.Optional;
-import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -83,12 +82,11 @@
     private final Resources mResources;
     private final BatteryController mBatteryController;
     private final ScreenOffAnimationController mScreenOffAnimationController;
+    private final DozeInteractor mDozeInteractor;
     private final FoldAodAnimationController mFoldAodAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private final UserTracker mUserTracker;
 
-    private final Set<Callback> mCallbacks = new HashSet<>();
-
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
     private boolean mIsQuickPickupEnabled;
@@ -131,7 +129,8 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController,
             StatusBarStateController statusBarStateController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            DozeInteractor dozeInteractor) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -144,6 +143,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mUserTracker = userTracker;
+        mDozeInteractor = dozeInteractor;
 
         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         tunerService.addTunable(
@@ -406,20 +406,6 @@
         return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping);
     }
 
-    /**
-     * Callback to listen for DozeParameter changes.
-     */
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-    }
-
-    /**
-     * Remove callback that listens for DozeParameter changes.
-     */
-    public void removeCallback(Callback callback) {
-        mCallbacks.remove(callback);
-    }
-
     @Override
     public void onTuningChanged(String key, String newValue) {
         mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(mUserTracker.getUserId());
@@ -465,10 +451,9 @@
     }
 
     private void dispatchAlwaysOnEvent() {
-        for (Callback callback : mCallbacks) {
-            callback.onAlwaysOnChange();
-        }
         mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
+        mDozeInteractor.setAodAvailable(getAlwaysOn());
+
     }
 
     private boolean getPostureSpecificBool(
@@ -485,14 +470,6 @@
         return bool;
     }
 
-    /** Callbacks for doze parameter related information */
-    public interface Callback {
-        /**
-         * Invoked when the value of getAlwaysOn may have changed.
-         */
-        void onAlwaysOnChange();
-    }
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri mQuickPickupGesture =
                 Settings.Secure.getUriFor(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index d3d11ea..600d4af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -37,6 +37,7 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -47,18 +48,18 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.Assert;
 
-import dagger.Lazy;
-
 import java.util.ArrayList;
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
@@ -82,6 +83,7 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final HeadsUpManager mHeadsUpManager;
+    private final FeatureFlagsClassic mFeatureFlags;
     private final BatteryController mBatteryController;
     private final ScrimController mScrimController;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -107,6 +109,7 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             SysuiStatusBarStateController statusBarStateController,
             DeviceProvisionedController deviceProvisionedController,
+            FeatureFlagsClassic featureFlags,
             HeadsUpManager headsUpManager, BatteryController batteryController,
             ScrimController scrimController,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
@@ -130,6 +133,7 @@
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
         mAssistManagerLazy = assistManagerLazy;
         mDozeScrimController = dozeScrimController;
+        mFeatureFlags = featureFlags;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPulseExpansionHandler = pulseExpansionHandler;
         mNotificationShadeWindowController = notificationShadeWindowController;
@@ -173,8 +177,13 @@
 
     void fireNotificationPulse(NotificationEntry entry) {
         Runnable pulseSuppressedListener = () -> {
-            entry.setPulseSuppressed(true);
-            mNotificationIconAreaController.updateAodNotificationIcons();
+            if (NotificationIconContainerRefactor.isEnabled()) {
+                mHeadsUpManager.removeNotification(
+                        entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
+            } else {
+                entry.setPulseSuppressed(true);
+                mNotificationIconAreaController.updateAodNotificationIcons();
+            }
         };
         Assert.isMainThread();
         for (Callback callback : mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c493eeda..beeee1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
@@ -38,7 +39,9 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
@@ -104,6 +107,8 @@
             };
     private boolean mAnimationsEnabled = true;
     private final KeyguardStateController mKeyguardStateController;
+    private final FeatureFlagsClassic mFeatureFlags;
+    private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor;
 
     @VisibleForTesting
     @Inject
@@ -122,6 +127,8 @@
             NotificationRoundnessManager notificationRoundnessManager,
             HeadsUpStatusBarView headsUpStatusBarView,
             Clock clockView,
+            FeatureFlagsClassic featureFlags,
+            HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
             @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
         mNotificationIconAreaController = notificationIconAreaController;
@@ -139,6 +146,8 @@
 
         mStackScrollerController = stackScrollerController;
         mShadeViewController = shadeViewController;
+        mFeatureFlags = featureFlags;
+        mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor;
         mStackScrollerController.setHeadsUpAppearanceController(this);
         mClockView = clockView;
         mOperatorNameViewOptional = operatorNameViewOptional;
@@ -170,6 +179,9 @@
         mHeadsUpManager.addListener(this);
         mView.setOnDrawingRectChangedListener(
                 () -> updateIsolatedIconLocation(true /* requireUpdate */));
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            updateIsolatedIconLocation(true);
+        }
         mWakeUpCoordinator.addListener(this);
         getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
         getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
@@ -185,6 +197,9 @@
     protected void onViewDetached() {
         mHeadsUpManager.removeListener(this);
         mView.setOnDrawingRectChangedListener(null);
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
+        }
         mWakeUpCoordinator.removeListener(this);
         getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
         getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
@@ -193,8 +208,13 @@
     }
 
     private void updateIsolatedIconLocation(boolean requireStateUpdate) {
-        mNotificationIconAreaController.setIsolatedIconLocation(
-                mView.getIconDrawingRect(), requireStateUpdate);
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            mHeadsUpNotificationIconInteractor
+                    .setIsolatedIconLocation(mView.getIconDrawingRect());
+        } else {
+            mNotificationIconAreaController.setIsolatedIconLocation(
+                    mView.getIconDrawingRect(), requireStateUpdate);
+        }
     }
 
     @Override
@@ -230,9 +250,14 @@
                 setShown(true);
                 animateIsolation = !isExpanded();
             }
-            updateIsolatedIconLocation(false /* requireUpdate */);
-            mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
-                    : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+            if (NotificationIconContainerRefactor.isEnabled()) {
+                mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
+                        newEntry == null ? null : newEntry.getKey());
+            } else {
+                updateIsolatedIconLocation(false /* requireUpdate */);
+                mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
+                        : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+            }
         }
     }
 
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 6b4382f73..3a95e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -34,7 +34,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -105,7 +106,8 @@
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  Constructor:
     @Inject
-    public HeadsUpManagerPhone(@NonNull final Context context,
+    public HeadsUpManagerPhone(
+            @NonNull final Context context,
             HeadsUpManagerLogger logger,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController,
@@ -115,7 +117,8 @@
             @Main Handler handler,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
-            ShadeExpansionStateManager shadeExpansionStateManager) {
+            JavaAdapter javaAdapter,
+            ShadeInteractor shadeInteractor) {
         super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
@@ -136,8 +139,7 @@
                 updateResources();
             }
         });
-
-        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -175,7 +177,7 @@
         if (!hasPinnedHeadsUp() || topEntry == null) {
             return null;
         } else {
-            if (topEntry.isChildInGroup()) {
+            if (topEntry.rowIsChildInGroup()) {
                 final NotificationEntry groupSummary =
                         mGroupMembershipManager.getGroupSummary(topEntry);
                 if (groupSummary != null) {
@@ -230,7 +232,7 @@
         mTrackingHeadsUp = trackingHeadsUp;
     }
 
-    private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+    private void onShadeOrQsExpanded(Boolean isExpanded) {
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
             if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 63591d7..b0183d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -58,7 +58,7 @@
     private var pendingUnlock: PendingUnlock? = null
     private val listeners = mutableListOf<OnBypassStateChangedListener>()
     private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
-        override fun onFaceAuthEnabledChanged() = notifyListeners()
+        override fun onFaceEnrolledChanged() = notifyListeners()
     }
 
     @IntDef(
@@ -98,7 +98,7 @@
                 FACE_UNLOCK_BYPASS_NEVER -> false
                 else -> field
             }
-            return enabled && mKeyguardStateController.isFaceAuthEnabled &&
+            return enabled && mKeyguardStateController.isFaceEnrolled &&
                     isPostureAllowedForFaceAuth()
         }
         private set(value) {
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 fb5a530..0a03af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -26,14 +26,20 @@
 import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.log.LogBuffer;
+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.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
  */
 public class KeyguardClockPositionAlgorithm {
+    private static final String TAG = "KeyguardClockPositionAlgorithm";
 
     /**
      * Margin between the bottom of the status view and the notification shade.
@@ -147,6 +153,13 @@
      */
     private boolean mIsClockTopAligned;
 
+    private Logger mLogger;
+
+    @Inject
+    public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) {
+        mLogger = new Logger(logBuffer, TAG);
+    }
+
     /**
      * Refreshes the dimension values.
      */
@@ -306,6 +319,20 @@
                 + fullyDarkBurnInOffset
                 + shift;
         mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
+        final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount;
+        final String outputs = "clockY: " + clockY
+                + " burnInPreventionOffsetY: " + burnInPreventionOffsetY
+                + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
+                + " shift: " + shift
+                + " mOverStretchAmount: " + mOverStretchAmount
+                + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
+        mLogger.i(msg -> {
+            return msg.getStr1() + " -> " + msg.getStr2();
+        }, msg -> {
+            msg.setStr1(inputs);
+            msg.setStr2(outputs);
+            return kotlin.Unit.INSTANCE;
+        });
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index bde5c32..3329844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.Assert
 import com.android.systemui.util.sensors.AsyncSensorManager
 import java.io.PrintWriter
@@ -48,7 +49,8 @@
     private val asyncSensorManager: AsyncSensorManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
-    private val dumpManager: DumpManager
+    private val dumpManager: DumpManager,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) : Dumpable, CoreStartable {
 
     private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
@@ -115,9 +117,7 @@
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
-        val userId = KeyguardUpdateMonitor.getCurrentUser()
-        val isFaceEnabled = keyguardUpdateMonitor.isFaceAuthEnabledForUser(userId)
-        val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
+        val shouldListen = (onKeyguard || bouncerVisible) && keyguardUpdateMonitor.isFaceEnrolled
         if (shouldListen != isListening) {
             isListening = shouldListen
 
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 58126ae..8a64a50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -436,10 +436,14 @@
     private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
         @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
+        float luminance = Color.luminance(textColor);
         @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
-                Color.luminance(textColor) < 0.5
+                    luminance < 0.5
                         ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
                         : com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+        @ColorInt int contrastColor = luminance < 0.5
+                ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
+                : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
         float intensity = textColor == Color.WHITE ? 0 : 1;
         mCarrierLabel.setTextColor(iconColor);
 
@@ -451,7 +455,7 @@
         }
 
         if (iconManager != null) {
-            iconManager.setTint(iconColor);
+            iconManager.setTint(iconColor, contrastColor);
         }
 
         mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2960520..2206be5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -546,8 +546,7 @@
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
         }
 
-        if (mSystemEventAnimator.isAnimationRunning()
-                && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+        if (mSystemEventAnimator.isAnimationRunning()) {
             newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
         } else {
             mView.setTranslationX(0);
@@ -704,21 +703,11 @@
 
     private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) {
         return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
-            // TODO(b/273443374): remove if-else condition
-            if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
-                mSystemEventAnimatorAlpha = alpha;
-            } else {
-                mSystemEventAnimatorAlpha = 1f;
-            }
+            mSystemEventAnimatorAlpha = alpha;
             updateViewState();
             return Unit.INSTANCE;
         }, (translationX) -> {
-            // TODO(b/273443374): remove if-else condition
-            if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
-                mView.setTranslationX(translationX);
-            } else {
-                mView.setTranslationX(0);
-            }
+            mView.setTranslationX(translationX);
             return Unit.INSTANCE;
         }, isAnimationRunning);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 1b9e5b3..dd32434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -36,16 +36,16 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -109,7 +109,7 @@
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final Context mContext;
 
-    private final ViewRefactorFlag mShelfRefactor;
+    private final RefactorFlag mShelfRefactor;
 
     private final boolean mNewAodTransition;
 
@@ -150,7 +150,7 @@
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
-        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
+        mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mNewAodTransition = featureFlags.isEnabled(NEW_AOD_TRANSITION);
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
@@ -205,14 +205,13 @@
     }
 
     public void setupShelf(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mShelfIcons = notificationShelfController.getShelfIcons();
     }
 
     public void setShelfIcons(NotificationIconContainer icons) {
-        if (mShelfRefactor.expectEnabled()) {
-            mShelfIcons = icons;
-        }
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+        mShelfIcons = icons;
     }
 
     public void onDensityOrFontScaleChanged(@NotNull Context context) {
@@ -449,7 +448,7 @@
             }
         }
         replacingIcons.removeAll(duplicates);
-        hostLayout.setReplacingIcons(replacingIcons);
+        hostLayout.setReplacingIconsLegacy(replacingIcons);
 
         final int toRemoveCount = toRemove.size();
         for (int i = 0; i < toRemoveCount; i++) {
@@ -482,7 +481,7 @@
             hostLayout.addView(expected, i);
         }
         hostLayout.setChangingViewPositions(false);
-        hostLayout.setReplacingIcons(null);
+        hostLayout.setReplacingIconsLegacy(null);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
deleted file mode 100644
index 92c786f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ /dev/null
@@ -1,435 +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.statusbar.phone;
-
-import android.annotation.Nullable;
-import android.app.IWallpaperManager;
-import android.app.IWallpaperManagerCallback;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-import android.graphics.Xfermode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableWrapper;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.user.data.model.SelectedUserModel;
-import com.android.systemui.user.data.model.SelectionStatus;
-import com.android.systemui.user.data.repository.UserRepository;
-import com.android.systemui.util.kotlin.JavaAdapter;
-
-import libcore.io.IoUtils;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-/**
- * Manages the lockscreen wallpaper.
- */
-@SysUISingleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
-        Dumpable, CoreStartable {
-
-    private static final String TAG = "LockscreenWallpaper";
-
-    // TODO(b/253507223): temporary; remove this
-    private static final String DISABLED_ERROR_MESSAGE = "Methods from LockscreenWallpaper.java "
-            + "should not be called in this version. The lock screen wallpaper should be "
-            + "managed by the WallpaperManagerService and not by this class.";
-
-    private final NotificationMediaManager mMediaManager;
-    private final WallpaperManager mWallpaperManager;
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final Handler mH;
-    private final JavaAdapter mJavaAdapter;
-    private final UserRepository mUserRepository;
-
-    private boolean mCached;
-    private Bitmap mCache;
-    private int mCurrentUserId;
-    // The user selected in the UI, or null if no user is selected or UI doesn't support selecting
-    // users.
-    private UserHandle mSelectedUser;
-    private AsyncTask<Void, Void, LoaderResult> mLoader;
-
-    @Inject
-    public LockscreenWallpaper(WallpaperManager wallpaperManager,
-            @Nullable IWallpaperManager iWallpaperManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DumpManager dumpManager,
-            NotificationMediaManager mediaManager,
-            @Main Handler mainHandler,
-            JavaAdapter javaAdapter,
-            UserRepository userRepository,
-            UserTracker userTracker) {
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
-        mWallpaperManager = wallpaperManager;
-        mCurrentUserId = userTracker.getUserId();
-        mUpdateMonitor = keyguardUpdateMonitor;
-        mMediaManager = mediaManager;
-        mH = mainHandler;
-        mJavaAdapter = javaAdapter;
-        mUserRepository = userRepository;
-
-        if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            // Service is disabled on some devices like Automotive
-            try {
-                iWallpaperManager.setLockWallpaperCallback(this);
-            } catch (RemoteException e) {
-                Log.e(TAG, "System dead?" + e);
-            }
-        }
-    }
-
-    @Override
-    public void start() {
-        if (!isLockscreenLiveWallpaperEnabled()) {
-            mJavaAdapter.alwaysCollectFlow(
-                    mUserRepository.getSelectedUser(), this::setSelectedUser);
-        }
-    }
-
-    public Bitmap getBitmap() {
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (mCached) {
-            return mCache;
-        }
-        if (!mWallpaperManager.isWallpaperSupported()) {
-            mCached = true;
-            mCache = null;
-            return null;
-        }
-
-        LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
-        if (result.success) {
-            mCached = true;
-            mCache = result.bitmap;
-        }
-        return mCache;
-    }
-
-    public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {
-        // May be called on any thread - only use thread safe operations.
-
-        assertLockscreenLiveWallpaperNotEnabled();
-
-
-        if (!mWallpaperManager.isWallpaperSupported()) {
-            // When wallpaper is not supported, show the system wallpaper
-            return LoaderResult.success(null);
-        }
-
-        // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
-        // wallpaper.
-        final int lockWallpaperUserId =
-                selectedUser != null ? selectedUser.getIdentifier() : currentUserId;
-        ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile(
-                WallpaperManager.FLAG_LOCK, lockWallpaperUserId);
-
-        if (fd != null) {
-            try {
-                BitmapFactory.Options options = new BitmapFactory.Options();
-                options.inPreferredConfig = Bitmap.Config.HARDWARE;
-                return LoaderResult.success(BitmapFactory.decodeFileDescriptor(
-                        fd.getFileDescriptor(), null, options));
-            } catch (OutOfMemoryError e) {
-                Log.w(TAG, "Can't decode file", e);
-                return LoaderResult.fail();
-            } finally {
-                IoUtils.closeQuietly(fd);
-            }
-        } else {
-            if (selectedUser != null) {
-                // Show the selected user's static wallpaper.
-                return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
-                        selectedUser.getIdentifier(), true /* hardware */));
-
-            } else {
-                // When there is no selected user, show the system wallpaper
-                return LoaderResult.success(null);
-            }
-        }
-    }
-
-    private void setSelectedUser(SelectedUserModel selectedUserModel) {
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
-            // Wait until the selection has finished before updating.
-            return;
-        }
-
-        int user = selectedUserModel.getUserInfo().id;
-        if (user != mCurrentUserId) {
-            if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
-                mCached = false;
-            }
-            mCurrentUserId = user;
-        }
-    }
-
-    public void setSelectedUser(UserHandle selectedUser) {
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (Objects.equals(selectedUser, mSelectedUser)) {
-            return;
-        }
-        mSelectedUser = selectedUser;
-        postUpdateWallpaper();
-    }
-
-    @Override
-    public void onWallpaperChanged() {
-        assertLockscreenLiveWallpaperNotEnabled();
-        // Called on Binder thread.
-        postUpdateWallpaper();
-    }
-
-    @Override
-    public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
-        assertLockscreenLiveWallpaperNotEnabled();
-    }
-
-    private void postUpdateWallpaper() {
-        assertLockscreenLiveWallpaperNotEnabled();
-        if (mH == null) {
-            Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");
-            return;
-        }
-        mH.removeCallbacks(this);
-        mH.post(this);
-    }
-    @Override
-    public void run() {
-        // Called in response to onWallpaperChanged on the main thread.
-
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (mLoader != null) {
-            mLoader.cancel(false /* interrupt */);
-        }
-
-        final int currentUser = mCurrentUserId;
-        final UserHandle selectedUser = mSelectedUser;
-        mLoader = new AsyncTask<Void, Void, LoaderResult>() {
-            @Override
-            protected LoaderResult doInBackground(Void... params) {
-                return loadBitmap(currentUser, selectedUser);
-            }
-
-            @Override
-            protected void onPostExecute(LoaderResult result) {
-                super.onPostExecute(result);
-                if (isCancelled()) {
-                    return;
-                }
-                if (result.success) {
-                    mCached = true;
-                    mCache = result.bitmap;
-                    mMediaManager.updateMediaMetaData(
-                            true /* metaDataChanged */, true /* allowEnterAnimation */);
-                }
-                mLoader = null;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-    }
-
-    // TODO(b/273443374): remove
-    public boolean isLockscreenLiveWallpaperEnabled() {
-        return mWallpaperManager.isLockscreenLiveWallpaperEnabled();
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println(getClass().getSimpleName() + ":");
-        IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "  ").increaseIndent();
-        iPw.println("mCached=" + mCached);
-        iPw.println("mCache=" + mCache);
-        iPw.println("mCurrentUserId=" + mCurrentUserId);
-        iPw.println("mSelectedUser=" + mSelectedUser);
-    }
-
-    private static class LoaderResult {
-        public final boolean success;
-        public final Bitmap bitmap;
-
-        LoaderResult(boolean success, Bitmap bitmap) {
-            this.success = success;
-            this.bitmap = bitmap;
-        }
-
-        static LoaderResult success(Bitmap b) {
-            return new LoaderResult(true, b);
-        }
-
-        static LoaderResult fail() {
-            return new LoaderResult(false, null);
-        }
-    }
-
-    /**
-     * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
-     *
-     * <p>Aligns to the center when showing on the smaller internal display of a multi display
-     * device.
-     */
-    public static class WallpaperDrawable extends DrawableWrapper {
-
-        private final ConstantState mState;
-        private final Rect mTmpRect = new Rect();
-        private boolean mIsOnSmallerInternalDisplays;
-
-        public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) {
-            this(r, new ConstantState(b), isOnSmallerInternalDisplays);
-        }
-
-        private WallpaperDrawable(Resources r, ConstantState state,
-                boolean isOnSmallerInternalDisplays) {
-            super(new BitmapDrawable(r, state.mBackground));
-            mState = state;
-            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
-        }
-
-        @Override
-        public void setXfermode(@Nullable Xfermode mode) {
-            // DrawableWrapper does not call this for us.
-            getDrawable().setXfermode(mode);
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return -1;
-        }
-
-        @Override
-        public int getIntrinsicHeight() {
-            return -1;
-        }
-
-        @Override
-        protected void onBoundsChange(Rect bounds) {
-            int vwidth = getBounds().width();
-            int vheight = getBounds().height();
-            int dwidth = mState.mBackground.getWidth();
-            int dheight = mState.mBackground.getHeight();
-            float scale;
-            float dx = 0, dy = 0;
-
-            if (dwidth * vheight > vwidth * dheight) {
-                scale = (float) vheight / (float) dheight;
-            } else {
-                scale = (float) vwidth / (float) dwidth;
-            }
-
-            if (scale <= 1f) {
-                scale = 1f;
-            }
-            dy = (vheight - dheight * scale) * 0.5f;
-
-            int offsetX = 0;
-            // Offset to show the center area of the wallpaper on a smaller display for multi
-            // display device
-            if (mIsOnSmallerInternalDisplays) {
-                offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2);
-            }
-
-            mTmpRect.set(
-                    bounds.left + offsetX,
-                    bounds.top + Math.round(dy),
-                    bounds.left + Math.round(dwidth * scale) + offsetX,
-                    bounds.top + Math.round(dheight * scale + dy));
-
-            super.onBoundsChange(mTmpRect);
-        }
-
-        @Override
-        public ConstantState getConstantState() {
-            return mState;
-        }
-
-        /**
-         * Update bounds when the hosting display or the display size has changed.
-         *
-         * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal
-         *                                    displays with the smaller area.
-         */
-        public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) {
-            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
-            onBoundsChange(getBounds());
-        }
-
-        static class ConstantState extends Drawable.ConstantState {
-
-            private final Bitmap mBackground;
-
-            ConstantState(Bitmap background) {
-                mBackground = background;
-            }
-
-            @Override
-            public Drawable newDrawable() {
-                return newDrawable(null);
-            }
-
-            @Override
-            public Drawable newDrawable(@Nullable Resources res) {
-                return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false);
-            }
-
-            @Override
-            public int getChangingConfigurations() {
-                // DrawableWrapper already handles this for us.
-                return 0;
-            }
-        }
-    }
-
-    /**
-     * Feature b/253507223 will adapt the logic to always use the
-     * WallpaperManagerService to render the lock screen wallpaper.
-     * Methods of this class should not be called at all if the project flag is enabled.
-     * TODO(b/253507223) temporary assertion; remove this
-     */
-    private void assertLockscreenLiveWallpaperNotEnabled() {
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            throw new IllegalStateException(DISABLED_ERROR_MESSAGE);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
index 0079f7c..2823b28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
@@ -28,7 +28,7 @@
  */
 interface NotificationIconAreaController {
     /** Called by the Keyguard*ViewController whose view contains the aod icons. */
-    fun setupAodIcons(aodIcons: NotificationIconContainer)
+    fun setupAodIcons(aodIcons: NotificationIconContainer?)
     fun setupShelf(notificationShelfController: NotificationShelfController)
     fun setShelfIcons(icons: NotificationIconContainer)
     fun onDensityOrFontScaleChanged(context: Context)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
index d1ddd51..ba69370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
@@ -15,9 +15,8 @@
  */
 package com.android.systemui.statusbar.phone
 
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import dagger.Module
 import dagger.Provides
 import javax.inject.Provider
@@ -26,11 +25,10 @@
 object NotificationIconAreaControllerModule {
     @Provides
     fun provideNotificationIconAreaControllerImpl(
-        featureFlags: FeatureFlags,
         legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
         newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
     ): NotificationIconAreaController =
-        if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+        if (NotificationIconContainerRefactor.isEnabled) {
             newProvider.get()
         } else {
             legacyProvider.get()
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 b15c0fd..efb8e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -42,6 +42,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -156,7 +157,8 @@
     private int mIconSize;
     private boolean mDisallowNextAnimation;
     private boolean mAnimationsEnabled = true;
-    private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
+    private ArrayMap<String, StatusBarIcon> mReplacingIcons;
+    private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIconsLegacy;
     // Keep track of the last visible icon so collapsed container can report on its location
     private IconState mLastVisibleIconState;
     private IconState mFirstVisibleIconState;
@@ -167,6 +169,7 @@
     private final int[] mAbsolutePosition = new int[2];
     private View mIsolatedIconForAnimation;
     private int mThemedTextColorPrimary;
+    private Runnable mIsolatedIconAnimationEndRunnable;
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -339,23 +342,29 @@
     }
 
     private boolean isReplacingIcon(View child) {
-        if (mReplacingIcons == null) {
-            return false;
-        }
         if (!(child instanceof StatusBarIconView)) {
             return false;
         }
         StatusBarIconView iconView = (StatusBarIconView) child;
         Icon sourceIcon = iconView.getSourceIcon();
         String groupKey = iconView.getNotification().getGroupKey();
-        ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
-        if (statusBarIcons != null) {
-            StatusBarIcon replacedIcon = statusBarIcons.get(0);
-            if (sourceIcon.sameAs(replacedIcon.icon)) {
-                return true;
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            if (mReplacingIcons == null) {
+                return false;
             }
+            StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
+            return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
+        } else {
+            if (mReplacingIconsLegacy == null) {
+                return false;
+            }
+            ArrayList<StatusBarIcon> statusBarIcons = mReplacingIconsLegacy.get(groupKey);
+            if (statusBarIcons != null) {
+                StatusBarIcon replacedIcon = statusBarIcons.get(0);
+                return sourceIcon.sameAs(replacedIcon.icon);
+            }
+            return false;
         }
-        return false;
     }
 
     @Override
@@ -681,14 +690,36 @@
         mAnimationsEnabled = enabled;
     }
 
-    public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+    public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+        NotificationIconContainerRefactor.assertInLegacyMode();
+        mReplacingIconsLegacy = replacingIcons;
+    }
+
+    public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
+        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mReplacingIcons = replacingIcons;
     }
 
+    @Deprecated
     public void showIconIsolated(StatusBarIconView icon, boolean animated) {
+        NotificationIconContainerRefactor.assertInLegacyMode();
         if (animated) {
-            mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+            showIconIsolatedAnimated(icon, null);
+        } else {
+            showIconIsolated(icon);
         }
+    }
+
+    public void showIconIsolatedAnimated(StatusBarIconView icon,
+            @Nullable Runnable onAnimationEnd) {
+        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
+        mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+        mIsolatedIconAnimationEndRunnable = onAnimationEnd;
+        showIconIsolated(icon);
+    }
+
+    public void showIconIsolated(StatusBarIconView icon) {
+        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
         mIsolatedIcon = icon;
         updateState();
     }
@@ -813,6 +844,11 @@
                             animationProperties = UNISOLATION_PROPERTY;
                             animationProperties.setDelay(
                                     mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0);
+                            Consumer<Property> endAction = getEndAction();
+                            if (endAction != null) {
+                                animationProperties.setAnimationEndAction(endAction);
+                                animationProperties.setAnimationCancelAction(endAction);
+                            }
                         } else {
                             animationProperties = UNISOLATION_PROPERTY_OTHERS;
                             animationProperties.setDelay(
@@ -836,6 +872,18 @@
             needsCannedAnimation = false;
         }
 
+        @Nullable
+        private Consumer<Property> getEndAction() {
+            if (mIsolatedIconAnimationEndRunnable == null) return null;
+            final Runnable endRunnable = mIsolatedIconAnimationEndRunnable;
+            return prop -> {
+                endRunnable.run();
+                if (mIsolatedIconAnimationEndRunnable == endRunnable) {
+                    mIsolatedIconAnimationEndRunnable = null;
+                }
+            };
+        }
+
         @Override
         public void initFrom(View view) {
             super.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 54d81b8..5a8b636 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -300,7 +300,8 @@
         mIconController.setIconVisibility(mSlotCast, false);
 
         // connected display
-        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display,
+                mResources.getString(R.string.connected_display_icon_desc));
         mIconController.setIconVisibility(mSlotConnectedDisplay, false);
 
         // hotspot
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 5b55264..744d70e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -16,6 +16,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.GONE;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import static java.lang.Float.isNaN;
@@ -51,7 +54,6 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dagger.SysUISingleton;
@@ -62,7 +64,9 @@
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 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.res.R;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -273,6 +277,7 @@
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsBouncerToGoneTransitionRunning = false;
     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+    private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel;
     private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
             (ScrimAlpha alphas) -> {
                 mInFrontAlpha = alphas.getFrontAlpha();
@@ -285,7 +290,7 @@
                 mScrimBehind.setViewAlpha(mBehindAlpha);
             };
 
-    Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
+    Consumer<TransitionStep> mBouncerToGoneTransition;
 
     @Inject
     public ScrimController(
@@ -304,6 +309,7 @@
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
@@ -349,6 +355,7 @@
         });
         mColors = new GradientColors();
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
+        mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
@@ -405,7 +412,7 @@
         // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure
         // to report back that keyguard has faded away. This fixes cases where the scrim state was
         // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl
-        mPrimaryBouncerToGoneTransition =
+        mBouncerToGoneTransition =
                 (TransitionStep step) -> {
                     TransitionState state = step.getTransitionState();
 
@@ -425,10 +432,17 @@
                     }
                 };
 
-        collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
-                mPrimaryBouncerToGoneTransition, mMainDispatcher);
+        // PRIMARY_BOUNCER->GONE
+        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE),
+                mBouncerToGoneTransition, mMainDispatcher);
         collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
                 mScrimAlphaConsumer, mMainDispatcher);
+
+        // ALTERNATE_BOUNCER->GONE
+        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE),
+                mBouncerToGoneTransition, mMainDispatcher);
+        collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
+                mScrimAlphaConsumer, mMainDispatcher);
     }
 
     // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
index 4d9de09..fa6d279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
@@ -3,12 +3,15 @@
 import android.app.StatusBarManager
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -25,14 +28,14 @@
  */
 @SysUISingleton
 class StatusBarHideIconsForBouncerManager @Inject constructor(
-        private val commandQueue: CommandQueue,
-        @Main private val mainExecutor: DelayableExecutor,
-        statusBarWindowStateController: StatusBarWindowStateController,
-        shadeExpansionStateManager: ShadeExpansionStateManager,
-        dumpManager: DumpManager
+    @Application private val scope: CoroutineScope,
+    private val commandQueue: CommandQueue,
+    @Main private val mainExecutor: DelayableExecutor,
+    statusBarWindowStateController: StatusBarWindowStateController,
+    val shadeInteractor: ShadeInteractor,
+    dumpManager: DumpManager
 ) : Dumpable {
     // State variables set by external classes.
-    private var panelExpanded: Boolean = false
     private var isOccluded: Boolean = false
     private var bouncerShowing: Boolean = false
     private var topAppHidesStatusBar: Boolean = false
@@ -49,10 +52,9 @@
         statusBarWindowStateController.addListener {
                 state -> setStatusBarStateAndTriggerUpdate(state)
         }
-        shadeExpansionStateManager.addFullExpansionListener { isExpanded ->
-            if (panelExpanded != isExpanded) {
-                panelExpanded = isExpanded
-                updateHideIconsForBouncer(animate = false)
+        scope.launch {
+            shadeInteractor.isAnyExpanded.collect {
+                updateHideIconsForBouncer(false)
             }
         }
     }
@@ -101,7 +103,7 @@
             topAppHidesStatusBar &&
                     isOccluded &&
                     (statusBarWindowHidden || bouncerShowing)
-        val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing
+        val hideBecauseKeyguard = !isShadeOrQsExpanded() && !isOccluded && bouncerShowing
         val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard
         if (hideIconsForBouncer != shouldHideIconsForBouncer) {
             hideIconsForBouncer = shouldHideIconsForBouncer
@@ -125,9 +127,13 @@
         }
     }
 
+    private fun isShadeOrQsExpanded(): Boolean {
+        return shadeInteractor.isAnyExpanded.value
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("---- State variables set externally ----")
-        pw.println("panelExpanded=$panelExpanded")
+        pw.println("isShadeOrQsExpanded=${isShadeOrQsExpanded()}")
         pw.println("isOccluded=$isOccluded")
         pw.println("bouncerShowing=$bouncerShowing")
         pw.println("topAppHideStatusBar=$topAppHidesStatusBar")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ffeb1a8..9ae4195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -31,11 +31,11 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 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.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
@@ -248,7 +248,10 @@
      *
      */
     class TintedIconManager extends IconManager {
+        // The main tint, used as the foreground in non layer drawables
         private int mColor;
+        // To be used as the main tint in drawables that wish to have a layer
+        private int mForegroundColor;
 
         public TintedIconManager(
                 ViewGroup group,
@@ -268,26 +271,41 @@
         protected void onIconAdded(int index, String slot, boolean blocked,
                 StatusBarIconHolder holder) {
             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
-            view.setStaticDrawableColor(mColor);
+            view.setStaticDrawableColor(mColor, mForegroundColor);
             view.setDecorColor(mColor);
         }
 
-        public void setTint(int color) {
-            mColor = color;
+        /**
+         * Most icons are a single layer, and tintColor will be used as the tint in those cases.
+         * For icons that have a background, foregroundColor becomes the contrasting tint used
+         * for the foreground.
+         *
+         * @param tintColor the main tint to use for the icons in the group
+         * @param foregroundColor used as the main tint for layer-ish drawables where tintColor is
+         *                        being used as the background
+         */
+        public void setTint(int tintColor, int foregroundColor) {
+            mColor = tintColor;
+            mForegroundColor = foregroundColor;
+
             for (int i = 0; i < mGroup.getChildCount(); i++) {
                 View child = mGroup.getChildAt(i);
                 if (child instanceof StatusIconDisplayable) {
                     StatusIconDisplayable icon = (StatusIconDisplayable) child;
-                    icon.setStaticDrawableColor(mColor);
+                    icon.setStaticDrawableColor(mColor, mForegroundColor);
                     icon.setDecorColor(mColor);
                 }
             }
+
+            if (mDemoStatusIcons != null) {
+                mDemoStatusIcons.setColor(tintColor, foregroundColor);
+            }
         }
 
         @Override
         protected DemoStatusIcons createDemoStatusIcons() {
             DemoStatusIcons icons = super.createDemoStatusIcons();
-            icons.setColor(mColor);
+            icons.setColor(mColor, mForegroundColor);
             return icons;
         }
 
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 62b2445..267b563 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -296,7 +297,6 @@
 
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
-    private final boolean mUdfpsNewTouchDetectionEnabled;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
     private final ActivityStarter mActivityStarter;
 
@@ -314,6 +314,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateManager;
     private final LatencyTracker mLatencyTracker;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     @Nullable private KeyguardBypassController mBypassController;
     @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
 
@@ -371,7 +372,8 @@
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
-            Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy
+            Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
+            SelectedUserInteractor selectedUserInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -398,13 +400,13 @@
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mIsBackAnimationEnabled =
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
-        mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mMainDispatcher = mainDispatcher;
         mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
         mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -962,9 +964,6 @@
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
         }
-        if (isShowing) {
-            mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
-        }
         mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
 
         // setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
@@ -1144,7 +1143,8 @@
      */
     public boolean isSecure() {
         return mKeyguardSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
+                mSelectedUserInteractor.getSelectedUserId())
+                != KeyguardSecurityModel.SecurityMode.None;
     }
 
     /**
@@ -1594,7 +1594,7 @@
             final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch()
                     && event.getActionMasked() == MotionEvent.ACTION_UP;
             final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade =
-                    mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled();
+                    mKeyguardUpdateManager.isUdfpsEnrolled();
             final boolean actionOutsideShouldDismissAlternateBouncer =
                     event.getActionMasked() == MotionEvent.ACTION_OUTSIDE
                     && !udfpsOverlayWillForwardEventsOutsideNotificationShade;
@@ -1692,7 +1692,7 @@
      */
     public boolean needsFullscreenBouncer() {
         KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         return mode == KeyguardSecurityModel.SecurityMode.SimPin
                 || mode == KeyguardSecurityModel.SecurityMode.SimPuk;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 2d14f6b..07e2571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -52,7 +52,7 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -80,7 +80,7 @@
     private final HeadsUpManager mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
-    private final NotificationsInteractor mNotificationsInteractor;
+    private final NotificationAlertsInteractor mNotificationAlertsInteractor;
     private final NotificationStackScrollLayoutController mNsslController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
     private final PowerInteractor mPowerInteractor;
@@ -107,7 +107,7 @@
             NotificationShadeWindowController notificationShadeWindowController,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
-            NotificationsInteractor notificationsInteractor,
+            NotificationAlertsInteractor notificationAlertsInteractor,
             LockscreenShadeTransitionController shadeTransitionController,
             PowerInteractor powerInteractor,
             CommandQueue commandQueue,
@@ -127,7 +127,7 @@
         mQsController = quickSettingsController;
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
-        mNotificationsInteractor = notificationsInteractor;
+        mNotificationAlertsInteractor = notificationAlertsInteractor;
         mNsslController = stackScrollerController;
         mShadeTransitionController = shadeTransitionController;
         mPowerInteractor = powerInteractor;
@@ -205,7 +205,6 @@
         // End old BaseStatusBar.userSwitched
         mCommandQueue.animateCollapsePanels();
         mMediaManager.clearCurrentMediaNotification();
-        updateMediaMetaData(true, false);
     }
 
     @Override
@@ -220,11 +219,6 @@
     }
 
     @Override
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
-    }
-
-    @Override
     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
@@ -309,7 +303,7 @@
 
         @Override
         public boolean suppressInterruptions(NotificationEntry entry) {
-            return !mNotificationsInteractor.areNotificationAlertsEnabled();
+            return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index ba73c10..6d8ec44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -39,6 +39,7 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -86,8 +87,9 @@
             ConfigurationController configurationController,
             HeadsUpManager headsUpManager,
             ShadeExpansionStateManager shadeExpansionStateManager,
+            ShadeInteractor shadeInteractor,
             Provider<SceneInteractor> sceneInteractor,
-            Provider<JavaAdapter> javaAdapter,
+            JavaAdapter javaAdapter,
             SceneContainerFlags sceneContainerFlags,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
@@ -126,12 +128,12 @@
         });
 
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
 
         if (sceneContainerFlags.isEnabled()) {
-            javaAdapter.get().alwaysCollectFlow(
+            javaAdapter.alwaysCollectFlow(
                     sceneInteractor.get().isVisible(),
-                    this::onShadeExpansionFullyChanged);
+                    this::onShadeOrQsExpanded);
         }
 
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -151,7 +153,7 @@
         pw.println(mTouchableRegion);
     }
 
-    private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+    private void onShadeOrQsExpanded(Boolean isExpanded) {
         if (isExpanded != mIsStatusBarExpanded) {
             mIsStatusBarExpanded = isExpanded;
             if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index 6dc8065..da91d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar.phone;
 
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+
+@WeaklyReferencedCallback
 public interface StatusBarWindowCallback {
     /**
      * Invoked when the internal state of NotificationShadeWindowControllerImpl changes.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
new file mode 100644
index 0000000..71e25e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.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.phone
+
+import android.app.Dialog
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager.LayoutParams.MATCH_PARENT
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+
+/** A dialog shown as a bottom sheet. */
+open class SystemUIBottomSheetDialog(
+    context: Context,
+    private val configurationController: ConfigurationController? = null,
+    theme: Int = R.style.Theme_SystemUI_Dialog
+) : Dialog(context, theme) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setupWindow()
+        setupEdgeToEdge()
+        setCanceledOnTouchOutside(true)
+    }
+
+    private fun setupWindow() {
+        window?.apply {
+            setType(TYPE_STATUS_BAR_SUB_PANEL)
+            addPrivateFlags(SYSTEM_FLAG_SHOW_FOR_ALL_USERS or PRIVATE_FLAG_NO_MOVE_ANIMATION)
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setGravity(Gravity.BOTTOM)
+            decorView.setPadding(0, 0, 0, 0)
+            attributes =
+                attributes.apply {
+                    fitInsetsSides = 0
+                    horizontalMargin = 0f
+                }
+        }
+    }
+
+    private fun setupEdgeToEdge() {
+        val edgeToEdgeHorizontally =
+            context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+        val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
+        val height = WRAP_CONTENT
+        window?.setLayout(width, height)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        configurationController?.addCallback(onConfigChanged)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        configurationController?.removeCallback(onConfigChanged)
+    }
+
+    /** Can be overridden by subclasses to receive config changed events. */
+    open fun onConfigurationChanged() {}
+
+    private val onConfigChanged =
+        object : ConfigurationListener {
+            override fun onConfigChanged(newConfig: Configuration?) {
+                super.onConfigChanged(newConfig)
+                setupEdgeToEdge()
+                onConfigurationChanged()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9d627af..2558645 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -45,6 +45,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
@@ -54,8 +55,14 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
- * Base class for dialogs that should appear over panels and keyguard.
+ * Class for dialogs that should appear over panels and keyguard.
+ *
+ * DO NOT SUBCLASS THIS. See {@link SystemUIDialog.Delegate} for an interface that enables
+ * customizing behavior via composition instead of inheritance. Clients should implement the
+ * Delegate class and then pass their implementation into the SystemUIDialog constructor.
  *
  * Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to
  * listeners on whether this dialog is showing.
@@ -72,6 +79,7 @@
 
     private final Context mContext;
     private final FeatureFlags mFeatureFlags;
+    private final Delegate mDelegate;
     @Nullable private final DismissReceiver mDismissReceiver;
     private final Handler mHandler = new Handler();
     private final SystemUIDialogManager mDialogManager;
@@ -101,18 +109,102 @@
                 Dependency.get(SystemUIDialogManager.class),
                 Dependency.get(SysUiState.class),
                 Dependency.get(BroadcastDispatcher.class),
-                Dependency.get(DialogLaunchAnimator.class));
+                Dependency.get(DialogLaunchAnimator.class),
+                new Delegate() {});
     }
 
-    public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+    @Inject
+    public SystemUIDialog(
+            @Application Context context,
+            FeatureFlags featureFlags,
+            SystemUIDialogManager systemUIDialogManager,
+            SysUiState sysUiState,
+            BroadcastDispatcher broadcastDispatcher,
+            DialogLaunchAnimator dialogLaunchAnimator) {
+        this(context,
+                DEFAULT_THEME,
+                DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                featureFlags,
+                systemUIDialogManager,
+                sysUiState,
+                broadcastDispatcher,
+                dialogLaunchAnimator,
+                new Delegate(){});
+    }
+
+    public static class Factory {
+        private final Context mContext;
+        private final FeatureFlags mFeatureFlags;
+        private final SystemUIDialogManager mSystemUIDialogManager;
+        private final SysUiState mSysUiState;
+        private final BroadcastDispatcher mBroadcastDispatcher;
+        private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+        @Inject
+        public Factory(
+                @Application Context context,
+                FeatureFlags featureFlags,
+                SystemUIDialogManager systemUIDialogManager,
+                SysUiState sysUiState,
+                BroadcastDispatcher broadcastDispatcher,
+                DialogLaunchAnimator dialogLaunchAnimator) {
+            mContext = context;
+            mFeatureFlags = featureFlags;
+            mSystemUIDialogManager = systemUIDialogManager;
+            mSysUiState = sysUiState;
+            mBroadcastDispatcher = broadcastDispatcher;
+            mDialogLaunchAnimator = dialogLaunchAnimator;
+        }
+
+        public SystemUIDialog create(Delegate delegate) {
+            return new SystemUIDialog(
+                    mContext,
+                    DEFAULT_THEME,
+                    DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                    mFeatureFlags,
+                    mSystemUIDialogManager,
+                    mSysUiState,
+                    mBroadcastDispatcher,
+                    mDialogLaunchAnimator,
+                    delegate);
+        }
+    }
+
+    public SystemUIDialog(
+            Context context,
+            int theme,
+            boolean dismissOnDeviceLock,
             FeatureFlags featureFlags,
             SystemUIDialogManager dialogManager,
             SysUiState sysUiState,
             BroadcastDispatcher broadcastDispatcher,
             DialogLaunchAnimator dialogLaunchAnimator) {
+        this(
+                context,
+                theme,
+                dismissOnDeviceLock,
+                featureFlags,
+                dialogManager,
+                sysUiState,
+                broadcastDispatcher,
+                dialogLaunchAnimator,
+                new Delegate(){});
+    }
+
+    public SystemUIDialog(
+            Context context,
+            int theme,
+            boolean dismissOnDeviceLock,
+            FeatureFlags featureFlags,
+            SystemUIDialogManager dialogManager,
+            SysUiState sysUiState,
+            BroadcastDispatcher broadcastDispatcher,
+            DialogLaunchAnimator dialogLaunchAnimator,
+            Delegate delegate) {
         super(context, theme);
         mContext = context;
         mFeatureFlags = featureFlags;
+        mDelegate = delegate;
 
         applyFlags(this);
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
@@ -127,7 +219,9 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        mDelegate.beforeCreate(this, savedInstanceState);
         super.onCreate(savedInstanceState);
+        mDelegate.onCreate(this, savedInstanceState);
 
         Configuration config = getContext().getResources().getConfiguration();
         mLastConfigurationWidthDp = config.screenWidthDp;
@@ -172,6 +266,8 @@
 
             updateWindowSize();
         }
+
+        mDelegate.onConfigurationChanged(this, configuration);
     }
 
     /**
@@ -212,7 +308,9 @@
      * Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()}
      * should override this method instead.
      */
-    protected void start() {}
+    protected void start() {
+        mDelegate.start(this);
+    }
 
     @Override
     protected final void onStop() {
@@ -234,7 +332,15 @@
      * Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()}
      * should override this method instead.
      */
-    protected void stop() {}
+    protected void stop() {
+        mDelegate.stop(this);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        mDelegate.onWindowFocusChanged(this, hasFocus);
+    }
 
     public void setShowForAllUsers(boolean show) {
         setShowForAllUsers(this, show);
@@ -353,7 +459,6 @@
         registerDismissListener(dialog, null);
     }
 
-
     /**
      * Registers a listener that dismisses the given dialog when it receives
      * the screen off / close system dialogs broadcast.
@@ -480,4 +585,42 @@
         }
     }
 
+    /**
+     * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}.
+     *
+     * Implement this interface and then pass an instance of your implementation to
+     * {@link SystemUIDialog.Factory#create(Delegate)}.
+     */
+    public interface Delegate {
+        /**
+         * Called before {@link AlertDialog#onCreate} is called.
+         */
+        default void beforeCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+        /**
+         * Called after {@link AlertDialog#onCreate} is called.
+         */
+        default void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+        /**
+         * Called after {@link AlertDialog#onStart} is called.
+         */
+        default void start(SystemUIDialog dialog) {}
+
+        /**
+         * Called after {@link AlertDialog#onStop} is called.
+         */
+        default void stop(SystemUIDialog dialog) {}
+
+        /**
+         * Called after {@link AlertDialog#onWindowFocusChanged(boolean)} is called.
+         */
+        default void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) {}
+
+        /**
+         * Called as part of
+         * {@link ViewRootImpl.ConfigChangedCallback#onConfigurationChanged(Configuration)}.
+         */
+        default void onConfigurationChanged(SystemUIDialog dialog, Configuration configuration) {}
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 2c15e27..de37170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.util.TraceUtils
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 
 /**
  * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -65,7 +67,8 @@
     private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
-    private val handler: Handler = Handler()
+    private val handler: Handler = Handler(),
+    private val featureFlags: FeatureFlags,
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
     private lateinit var centralSurfaces: CentralSurfaces
     private lateinit var shadeViewController: ShadeViewController
@@ -285,7 +288,11 @@
                 // up, with unpredictable consequences.
                 if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
                         shouldAnimateInKeyguard) {
-                    aodUiAnimationPlaying = true
+                    if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        // Tracking this state should no longer be relevant, as the isInteractive
+                        // check covers it
+                        aodUiAnimationPlaying = true
+                    }
 
                     // Show AOD. That'll cause the KeyguardVisibilityHelper to call
                     // #animateInKeyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
index 9b0c3fa..9645c69 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
@@ -13,13 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.statusbar.phone.data
 
-package com.android.systemui.common.ui.data.repository
-
-import dagger.Binds
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepositoryModule
 import dagger.Module
 
-@Module
-interface CommonRepositoryModule {
-    @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-}
+@Module(includes = [DarkIconRepositoryModule::class]) object StatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
new file mode 100644
index 0000000..ba377497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Dark-mode state for tinting icons. */
+interface DarkIconRepository {
+    val darkState: StateFlow<DarkChange>
+}
+
+@SysUISingleton
+class DarkIconRepositoryImpl
+@Inject
+constructor(
+    darkIconDispatcher: SysuiDarkIconDispatcher,
+) : DarkIconRepository {
+    override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow()
+}
+
+@Module
+interface DarkIconRepositoryModule {
+    @Binds fun bindImpl(impl: DarkIconRepositoryImpl): DarkIconRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
new file mode 100644
index 0000000..246645e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** States pertaining to calculating colors for icons in dark mode. */
+class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
+    /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
+    val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
+    /**
+     * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
+     */
+    val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
+    /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
+    val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 63c022c..3921e69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -38,9 +38,12 @@
 import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.common.ui.ConfigurationState;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -52,7 +55,12 @@
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -66,6 +74,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -74,6 +83,8 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -83,7 +94,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import kotlin.Unit;
 
 /**
  * Contains the collapsed status bar and handles hiding/showing based on disable flags
@@ -128,7 +138,7 @@
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarLocationPublisher mLocationPublisher;
-    private final FeatureFlags mFeatureFlags;
+    private final FeatureFlagsClassic mFeatureFlags;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
@@ -142,6 +152,11 @@
     private final DumpManager mDumpManager;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
+    private final ConfigurationState mConfigurationState;
+    private final ConfigurationController mConfigurationController;
+    private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
+    private final DemoModeController mDemoModeController;
 
     private List<String> mBlockedIcons = new ArrayList<>();
     private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -211,9 +226,9 @@
             StatusBarLocationPublisher locationPublisher,
             NotificationIconAreaController notificationIconAreaController,
             ShadeExpansionStateManager shadeExpansionStateManager,
-            FeatureFlags featureFlags,
+            FeatureFlagsClassic featureFlags,
             StatusBarIconController statusBarIconController,
-            StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
+            DarkIconManager.Factory darkIconManagerFactory,
             CollapsedStatusBarViewModel collapsedStatusBarViewModel,
             CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
@@ -228,8 +243,12 @@
             @Main Executor mainExecutor,
             DumpManager dumpManager,
             StatusBarWindowStateController statusBarWindowStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor
-    ) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
+            ConfigurationState configurationState,
+            ConfigurationController configurationController,
+            StatusBarNotificationIconViewStore statusBarIconViewStore,
+            DemoModeController demoModeController) {
         mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
@@ -254,18 +273,53 @@
         mDumpManager = dumpManager;
         mStatusBarWindowStateController = statusBarWindowStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mStatusBarIconsViewModel = statusBarIconsViewModel;
+        mConfigurationState = configurationState;
+        mConfigurationController = configurationController;
+        mStatusBarIconViewStore = statusBarIconViewStore;
+        mDemoModeController = demoModeController;
     }
 
+    private final DemoMode mDemoModeCallback = new DemoMode() {
+        @Override
+        public List<String> demoCommands() {
+            return List.of(DemoMode.COMMAND_NOTIFICATIONS);
+        }
+
+        @Override
+        public void dispatchDemoCommand(String command, Bundle args) {
+            if (mNotificationIconAreaInner == null) return;
+            String visible = args.getString("visible");
+            if ("false".equals(visible)) {
+                mNotificationIconAreaInner.setVisibility(View.INVISIBLE);
+            } else {
+                mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+            }
+        }
+
+        @Override
+        public void onDemoModeFinished() {
+            if (mNotificationIconAreaInner == null) return;
+            mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+        }
+    };
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            mDemoModeController.addCallback(mDemoModeCallback);
+        }
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            mDemoModeController.removeCallback(mDemoModeCallback);
+        }
     }
 
     @Override
@@ -405,14 +459,28 @@
 
     /** Initializes views related to the notification icon area. */
     public void initNotificationIconArea() {
-        ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
-        mNotificationIconAreaInner =
-                mNotificationIconAreaController.getNotificationInnerAreaView();
-        if (mNotificationIconAreaInner.getParent() != null) {
-            ((ViewGroup) mNotificationIconAreaInner.getParent())
-                    .removeView(mNotificationIconAreaInner);
+        ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
+        if (NotificationIconContainerRefactor.isEnabled()) {
+            mNotificationIconAreaInner =
+                LayoutInflater.from(getContext())
+                        .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+            NotificationIconContainer notificationIcons =
+                    notificationIconArea.requireViewById(R.id.notificationIcons);
+            NotificationIconContainerViewBinder.bind(
+                    notificationIcons,
+                    mStatusBarIconsViewModel,
+                    mConfigurationState,
+                    mConfigurationController,
+                    mStatusBarIconViewStore);
+        } else {
+            mNotificationIconAreaInner =
+                    mNotificationIconAreaController.getNotificationInnerAreaView();
+            if (mNotificationIconAreaInner.getParent() != null) {
+                ((ViewGroup) mNotificationIconAreaInner.getParent())
+                        .removeView(mNotificationIconAreaInner);
+            }
+            notificationIconArea.addView(mNotificationIconAreaInner);
         }
-        notificationIconArea.addView(mNotificationIconAreaInner);
 
         updateNotificationIconAreaAndCallChip(/* animate= */ false);
     }
@@ -526,7 +594,7 @@
 
         // Hide notifications if the disable flag is set or we have an ongoing call.
         if (disableNotifications || hasOngoingCall) {
-            hideNotificationIconArea(animate);
+            hideNotificationIconArea(animate && !hasOngoingCall);
         } else {
             showNotificationIconArea(animate);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
index 8ff9198..8862c77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.pipeline.airplane.data.repository
 
 import android.os.Handler
-import android.os.UserHandle
 import android.provider.Settings.Global
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -66,13 +65,7 @@
     override val isAirplaneMode: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val observer =
-                    object :
-                        SettingObserver(
-                            globalSettings,
-                            bgHandler,
-                            Global.AIRPLANE_MODE_ON,
-                            UserHandle.USER_ALL
-                        ) {
+                    object : SettingObserver(globalSettings, bgHandler, Global.AIRPLANE_MODE_ON) {
                         override fun handleValueChanged(value: Int, observedChange: Boolean) {
                             trySend(value == 1)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index aacdc63..3522b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -190,6 +190,24 @@
     fun logOnSimStateChanged() {
         buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")
     }
+
+    fun logPrioritizedNetworkAvailable(netId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = netId },
+            { "Found prioritized network (nedId=$int1)" },
+        )
+    }
+
+    fun logPrioritizedNetworkLost(netId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = netId },
+            { "Lost prioritized network (nedId=$int1)" },
+        )
+    }
 }
 
 private const val TAG = "MobileInputLog"
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 a89b1b2..679426d 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
@@ -131,6 +131,12 @@
      */
     val isAllowedDuringAirplaneMode: StateFlow<Boolean>
 
+    /**
+     * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
+     * network slice
+     */
+    val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
+
     companion object {
         /** The default number of levels to use for [numberOfLevels]. */
         const val DEFAULT_NUM_LEVELS = 4
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index c576b82..caa9d1a 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
@@ -191,6 +191,8 @@
 
     override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
 
+    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
     /**
      * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
      * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
@@ -225,6 +227,7 @@
         _resolvedNetworkType.value = resolvedNetworkType
 
         isAllowedDuringAirplaneMode.value = false
+        hasPrioritizedNetworkCapabilities.value = event.slice
     }
 
     fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
@@ -250,6 +253,7 @@
         _isGsm.value = false
         _carrierNetworkChangeActive.value = false
         isAllowedDuringAirplaneMode.value = true
+        hasPrioritizedNetworkCapabilities.value = false
     }
 
     companion object {
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 d4ddb85..4cd877e 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
@@ -76,6 +76,7 @@
         val carrierNetworkChange = getString("carriernetworkchange") == "show"
         val roaming = getString("roam") == "show"
         val name = getString("networkname") ?: "demo mode"
+        val slice = getString("slice").toBoolean()
 
         return Mobile(
             level = level,
@@ -87,6 +88,7 @@
             carrierNetworkChange = carrierNetworkChange,
             roaming = roaming,
             name = name,
+            slice = slice,
         )
     }
 }
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 8b03f71..0aa95f8 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
@@ -36,6 +36,7 @@
         val carrierNetworkChange: Boolean,
         val roaming: Boolean,
         val name: String,
+        val slice: 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 28be3be..27edd1e 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
@@ -174,6 +174,13 @@
      */
     override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
 
+    /**
+     * It's not currently considered possible that a carrier merged network can have these
+     * prioritized capabilities. If we need to track them, we can add the same check as is in
+     * [MobileConnectionRepositoryImpl].
+     */
+    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
+
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index ee11c06..6b61921 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
@@ -309,6 +309,15 @@
                 activeRepo.value.isAllowedDuringAirplaneMode.value,
             )
 
+    override val hasPrioritizedNetworkCapabilities =
+        activeRepo
+            .flatMapLatest { it.hasPrioritizedNetworkCapabilities }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.hasPrioritizedNetworkCapabilities.value,
+            )
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index dc50990..760dd7e 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
@@ -21,6 +21,11 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
 import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
@@ -91,6 +96,7 @@
     subscriptionModel: StateFlow<SubscriptionModel?>,
     defaultNetworkName: NetworkNameModel,
     networkNameSeparator: String,
+    connectivityManager: ConnectivityManager,
     private val telephonyManager: TelephonyManager,
     systemUiCarrierConfig: SystemUiCarrierConfig,
     broadcastDispatcher: BroadcastDispatcher,
@@ -374,11 +380,50 @@
     /** Typical mobile connections aren't available during airplane mode. */
     override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
 
+    /**
+     * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
+     * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
+     * these capabilities that are used here must also be represented in the
+     * self_certified_network_capabilities.xml config file
+     */
+    @SuppressLint("WrongConstant")
+    private val networkSliceRequest =
+        NetworkRequest.Builder()
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+            .setSubscriptionIds(setOf(subId))
+            .build()
+
+    @SuppressLint("MissingPermission")
+    override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                // Our network callback listens only for this.subId && net_cap_prioritize_latency
+                // therefore our state is a simple mapping of whether or not that network exists
+                val callback =
+                    object : NetworkCallback() {
+                        override fun onAvailable(network: Network) {
+                            logger.logPrioritizedNetworkAvailable(network.netId)
+                            trySend(true)
+                        }
+
+                        override fun onLost(network: Network) {
+                            logger.logPrioritizedNetworkLost(network.netId)
+                            trySend(false)
+                        }
+                    }
+
+                connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
+
+                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+            }
+            .flowOn(bgDispatcher)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     class Factory
     @Inject
     constructor(
         private val context: Context,
         private val broadcastDispatcher: BroadcastDispatcher,
+        private val connectivityManager: ConnectivityManager,
         private val telephonyManager: TelephonyManager,
         private val logger: MobileInputLogger,
         private val carrierConfigRepository: CarrierConfigRepository,
@@ -399,6 +444,7 @@
                 subscriptionModel,
                 defaultNetworkName,
                 networkNameSeparator,
+                connectivityManager,
                 telephonyManager.createForSubscriptionId(subId),
                 carrierConfigRepository.getOrCreateConfigForSubId(subId),
                 broadcastDispatcher,
@@ -421,11 +467,17 @@
  */
 sealed interface CallbackEvent {
     data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+
     data class OnDataActivity(val direction: Int) : CallbackEvent
+
     data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+
     data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+
     data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
+
     data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
+
     data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
 }
 
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 4bf297c..fe49c07 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
@@ -76,6 +76,9 @@
     /** Observable for RAT type (network type) indicator */
     val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
 
+    /** Whether or not to show the slice attribution */
+    val showSliceAttribution: 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
@@ -238,6 +241,9 @@
                 DefaultIcon(defaultMobileIconGroup.value),
             )
 
+    override val showSliceAttribution: StateFlow<Boolean> =
+        connectionRepository.hasPrioritizedNetworkCapabilities
+
     override val isRoaming: StateFlow<Boolean> =
         combine(
                 connectionRepository.carrierNetworkChangeActive,
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 f0470ca..b93e443 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
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.binder
 
+import android.annotation.ColorInt
 import android.content.res.ColorStateList
 import android.view.View
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.Space
 import androidx.core.view.isVisible
@@ -28,10 +30,11 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.graph.SignalDrawable
-import com.android.systemui.res.R
 import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.DarkIconDispatcher
+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.ui.MobileViewLogger
@@ -43,6 +46,11 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
+private data class Colors(
+    @ColorInt val tint: Int,
+    @ColorInt val contrast: Int,
+)
+
 object MobileIconBinder {
     /** Binds the view to the view-model, continuing to update the former based on the latter */
     @JvmStatic
@@ -57,6 +65,7 @@
         val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
         val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
         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 roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
@@ -70,7 +79,13 @@
         @StatusBarIconView.VisibleState
         val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState)
 
-        val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+        val iconTint: MutableStateFlow<Colors> =
+            MutableStateFlow(
+                Colors(
+                    tint = DarkIconDispatcher.DEFAULT_ICON_TINT,
+                    contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT
+                )
+            )
         val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
 
         var isCollecting = false
@@ -139,7 +154,26 @@
                                 dataTypeId,
                             )
                             dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
-                            networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+                            networkTypeContainer.visibility =
+                                if (dataTypeId != null) VISIBLE else GONE
+                        }
+                    }
+
+                    // Set the network type background
+                    launch {
+                        viewModel.networkTypeBackground.collect { background ->
+                            networkTypeContainer.setBackgroundResource(background?.res ?: 0)
+
+                            // Tint will invert when this bit changes
+                            if (background?.res != null) {
+                                networkTypeContainer.backgroundTintList =
+                                    ColorStateList.valueOf(iconTint.value.tint)
+                                networkTypeView.imageTintList =
+                                    ColorStateList.valueOf(iconTint.value.contrast)
+                            } else {
+                                networkTypeView.imageTintList =
+                                    ColorStateList.valueOf(iconTint.value.tint)
+                            }
                         }
                     }
 
@@ -164,14 +198,24 @@
 
                     // Set the tint
                     launch {
-                        iconTint.collect { tint ->
-                            val tintList = ColorStateList.valueOf(tint)
-                            iconView.imageTintList = tintList
-                            networkTypeView.imageTintList = tintList
-                            roamingView.imageTintList = tintList
-                            activityIn.imageTintList = tintList
-                            activityOut.imageTintList = tintList
-                            dotView.setDecorColor(tint)
+                        iconTint.collect { colors ->
+                            val tint = ColorStateList.valueOf(colors.tint)
+                            val contrast = ColorStateList.valueOf(colors.contrast)
+
+                            iconView.imageTintList = tint
+
+                            // If the bg is visible, tint it and use the contrast for the fg
+                            if (viewModel.networkTypeBackground.value != null) {
+                                networkTypeContainer.backgroundTintList = tint
+                                networkTypeView.imageTintList = contrast
+                            } else {
+                                networkTypeView.imageTintList = tint
+                            }
+
+                            roamingView.imageTintList = tint
+                            activityIn.imageTintList = tint
+                            activityOut.imageTintList = tint
+                            dotView.setDecorColor(colors.tint)
                         }
                     }
 
@@ -196,8 +240,8 @@
                 visibilityState.value = state
             }
 
-            override fun onIconTintChanged(newTint: Int) {
-                iconTint.value = newTint
+            override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+                iconTint.value = Colors(tint = newTint, contrast = contrastTint)
             }
 
             override fun onDecorTintChanged(newTint: Int) {
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 dfabeea..d88c9ef 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
@@ -19,7 +19,10 @@
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -47,6 +50,8 @@
     val roaming: Flow<Boolean>
     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
     val networkTypeIcon: Flow<Icon.Resource?>
+    /** The slice attribution. Drawn as a background layer */
+    val networkTypeBackground: StateFlow<Icon.Resource?>
     val activityInVisible: Flow<Boolean>
     val activityOutVisible: Flow<Boolean>
     val activityContainerVisible: Flow<Boolean>
@@ -67,12 +72,12 @@
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
-class MobileIconViewModel
-constructor(
+class MobileIconViewModel(
     override val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     airplaneModeInteractor: AirplaneModeInteractor,
     constants: ConnectivityConstants,
+    flags: FeatureFlagsClassic,
     scope: CoroutineScope,
 ) : MobileIconViewModelCommon {
     override val isVisible: StateFlow<Boolean> =
@@ -152,6 +157,20 @@
             .distinctUntilChanged()
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
+    override val networkTypeBackground =
+        if (!flags.isEnabled(NEW_NETWORK_SLICE_UI)) {
+                flowOf(null)
+            } else {
+                iconInteractor.showSliceAttribution.map {
+                    if (it) {
+                        Icon.Resource(R.drawable.mobile_network_type_background, null)
+                    } else {
+                        null
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
     override val roaming: StateFlow<Boolean> =
         iconInteractor.isRoaming
             .logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 0f55910..be843ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
@@ -54,6 +55,7 @@
     private val interactor: MobileIconsInteractor,
     private val airplaneModeInteractor: AirplaneModeInteractor,
     private val constants: ConnectivityConstants,
+    private val flags: FeatureFlagsClassic,
     @Application private val scope: CoroutineScope,
 ) {
     @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
@@ -113,6 +115,7 @@
                     interactor.getMobileConnectionInteractorForSubId(subId),
                     airplaneModeInteractor,
                     constants,
+                    flags,
                     scope,
                 )
                 .also { mobileIconSubIdCache[subId] = it }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index 81f8683..790596e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -32,8 +32,8 @@
     /** Notifies that the visibility state has changed. */
     fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
 
-    /** Notifies that the icon tint has been updated. */
-    fun onIconTintChanged(newTint: Int)
+    /** Notifies that the icon tint has been updated. Includes a contrast for layered drawables */
+    fun onIconTintChanged(newTint: Int, contrastTint: Int)
 
     /** Notifies that the decor tint has been updated (used only for the dot). */
     fun onDecorTintChanged(newTint: Int)
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 fe69d81..3b87bed 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
@@ -20,8 +20,8 @@
 import android.graphics.Rect
 import android.util.AttributeSet
 import android.view.Gravity
-import com.android.systemui.res.R
 import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,13 +51,23 @@
     override fun getSlot() = slot
 
     override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        // nop
+    }
+
+    override fun onDarkChangedWithContrast(areas: ArrayList<Rect>, tint: Int, contrastTint: Int) {
         val newTint = DarkIconDispatcher.getTint(areas, this, tint)
-        binding.onIconTintChanged(newTint)
+        val contrast = DarkIconDispatcher.getInverseTint(areas, this, contrastTint)
+
+        binding.onIconTintChanged(newTint, contrast)
         binding.onDecorTintChanged(newTint)
     }
 
     override fun setStaticDrawableColor(color: Int) {
-        binding.onIconTintChanged(color)
+        // nop
+    }
+
+    override fun setStaticDrawableColor(color: Int, foregroundColor: Int) {
+        binding.onIconTintChanged(color, foregroundColor)
     }
 
     override fun setDecorColor(color: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index e9e52a2..1670dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -104,7 +104,7 @@
                 val callback =
                     object : WifiPickerTracker.WifiPickerTrackerCallback {
                         override fun onWifiEntriesChanged() {
-                            val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
                             logOnWifiEntriesChanged(connectedEntry)
 
                             val secondaryNetworks =
@@ -217,6 +217,21 @@
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
     /**
+     * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+     * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+     * if it exists, falling back on the connected entry if null
+     */
+    private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+        get() {
+            val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+            return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+                mergedEntry
+            } else {
+                this?.connectedWifiEntry
+            }
+        }
+
+    /**
      * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
      * primary network. Returns an inactive network if it's not primary.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index a9ac51d..6005527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -23,9 +23,9 @@
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.common.ui.binder.IconViewBinder
 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
@@ -165,7 +165,7 @@
                 visibilityState.value = state
             }
 
-            override fun onIconTintChanged(newTint: Int) {
+            override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) {
                 iconTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 945cc6b..53b343c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -27,6 +27,7 @@
 import android.os.UserManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.bluetooth.BluetoothCallback;
@@ -36,6 +37,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
@@ -81,6 +83,8 @@
     private int mState;
 
     private final BluetoothAdapter mAdapter;
+
+    private final Executor mBackgroundExecutor;
     /**
      */
     @Inject
@@ -90,6 +94,7 @@
             DumpManager dumpManager,
             BluetoothLogger logger,
             BluetoothRepository bluetoothRepository,
+            @Background Executor executor,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Nullable BluetoothAdapter bluetoothAdapter) {
@@ -98,6 +103,7 @@
         mBluetoothRepository = bluetoothRepository;
         mLocalBluetoothManager = localBluetoothManager;
         mHandler = new H(mainLooper);
+        mBackgroundExecutor = executor;
         if (mLocalBluetoothManager != null) {
             mLocalBluetoothManager.getEventManager().registerCallback(this);
             mLocalBluetoothManager.getProfileManager().addServiceListener(this);
@@ -218,6 +224,7 @@
         return mIsActive;
     }
 
+    @WorkerThread
     @Override
     public void setBluetoothEnabled(boolean enabled) {
         if (mLocalBluetoothManager != null) {
@@ -230,6 +237,7 @@
         return mLocalBluetoothManager != null;
     }
 
+    @WorkerThread
     @Override
     public String getConnectedDeviceName() {
         synchronized (mConnectedDevices) {
@@ -251,6 +259,7 @@
                 getDevices(), this::onConnectionStatusFetched);
     }
 
+    // Careful! This may be invoked in the main thread.
     private void onConnectionStatusFetched(ConnectionStatusModel status) {
         List<CachedBluetoothDevice> newList = status.getConnectedDevices();
         int state = status.getMaxConnectionState();
@@ -282,30 +291,33 @@
     }
 
     private void updateAudioProfile() {
-        boolean audioProfileConnected = false;
-        boolean otherProfileConnected = false;
+        // We want this in the background as calls inside `LocalBluetoothProfile` end up being
+        // binder calls
+        mBackgroundExecutor.execute(() -> {
+            boolean audioProfileConnected = false;
+            boolean otherProfileConnected = false;
 
-        for (CachedBluetoothDevice device : getDevices()) {
-            for (LocalBluetoothProfile profile : device.getProfiles()) {
-                int profileId = profile.getProfileId();
-                boolean isConnected = device.isConnectedProfile(profile);
-                if (profileId == BluetoothProfile.HEADSET
-                        || profileId == BluetoothProfile.A2DP
-                        || profileId == BluetoothProfile.HEARING_AID
-                        || profileId == BluetoothProfile.LE_AUDIO) {
-                    audioProfileConnected |= isConnected;
-                } else {
-                    otherProfileConnected |= isConnected;
+            for (CachedBluetoothDevice device : getDevices()) {
+                for (LocalBluetoothProfile profile : device.getProfiles()) {
+                    int profileId = profile.getProfileId();
+                    boolean isConnected = device.isConnectedProfile(profile);
+                    if (profileId == BluetoothProfile.HEADSET
+                            || profileId == BluetoothProfile.A2DP
+                            || profileId == BluetoothProfile.HEARING_AID
+                            || profileId == BluetoothProfile.LE_AUDIO) {
+                        audioProfileConnected |= isConnected;
+                    } else {
+                        otherProfileConnected |= isConnected;
+                    }
                 }
             }
-        }
 
-        boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
-        if (audioProfileOnly != mAudioProfileOnly) {
-            mAudioProfileOnly = audioProfileOnly;
-            mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
-        }
-
+            boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
+            if (audioProfileOnly != mAudioProfileOnly) {
+                mAudioProfileOnly = audioProfileOnly;
+                mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+            }
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
index 21acfb4..0a2bbe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -13,7 +13,8 @@
  */
 package com.android.systemui.statusbar.policy
 
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import android.content.res.Configuration
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 
@@ -23,14 +24,47 @@
  * @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged
  */
 val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit>
-    get() =
-        ConflatedCallbackFlow.conflatedCallbackFlow {
-            val listener =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onDensityOrFontScaleChanged() {
-                        trySend(Unit)
-                    }
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onDensityOrFontScaleChanged() {
+                    trySend(Unit)
                 }
-            addCallback(listener)
-            awaitClose { removeCallback(listener) }
-        }
+            }
+        addCallback(listener)
+        awaitClose { removeCallback(listener) }
+    }
+
+/**
+ * A [Flow] that emits whenever the theme has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onThemeChanged
+ */
+val ConfigurationController.onThemeChanged: Flow<Unit>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onThemeChanged() {
+                    trySend(Unit)
+                }
+            }
+        addCallback(listener)
+        awaitClose { removeCallback(listener) }
+    }
+
+/**
+ * A [Flow] that emits whenever the configuration has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onConfigChanged
+ */
+val ConfigurationController.onConfigChanged: Flow<Configuration>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySend(newConfig)
+                }
+            }
+        addCallback(listener)
+        awaitClose { removeCallback(listener) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 8c61ada..8b20283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -62,7 +62,7 @@
     }
 
     private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED)
-    private val frpActiveUri = secureSettings.getUriFor(Settings.Secure.SECURE_FRP_MODE)
+    private val frpActiveUri = globalSettings.getUriFor(Settings.Global.SECURE_FRP_MODE)
     private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
 
     private val deviceProvisioned = AtomicBoolean(false)
@@ -148,7 +148,7 @@
                     .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
         }
         if (updateFrp) {
-            frpActive.set(globalSettings.getInt(Settings.Secure.SECURE_FRP_MODE, 0) != 0)
+            frpActive.set(globalSettings.getInt(Settings.Global.SECURE_FRP_MODE, 0) != 0)
         }
         synchronized(lock) {
             if (updateUser == ALL_USERS) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 62e2381..b614b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.user.UserSwitchDialogController;
@@ -148,6 +149,7 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             UserSwitchDialogController userSwitchDialogController,
+            FeatureFlags featureFlags,
             UiEventLogger uiEventLogger) {
         super(view);
         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -160,7 +162,8 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
-                screenOffAnimationController,  /* animateYPos= */ false, /* logBuffer= */ null);
+                screenOffAnimationController,  /* animateYPos= */ false,
+                featureFlags, /* logBuffer= */ null);
         mUserSwitchDialogController = userSwitchDialogController;
         mUiEventLogger = uiEventLogger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 8929e02..52133ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -130,7 +130,7 @@
     /**
      * If there are faces enrolled and user enabled face auth on keyguard.
      */
-    default boolean isFaceAuthEnabled() {
+    default boolean isFaceEnrolled() {
         return false;
     }
 
@@ -265,9 +265,9 @@
 
         /**
          * Triggered when face auth becomes available or unavailable. Value should be queried with
-         * {@link KeyguardStateController#isFaceAuthEnabled()}.
+         * {@link KeyguardStateController#isFaceEnrolled()}.
          */
-        default void onFaceAuthEnabledChanged() {}
+        default void onFaceEnrolledChanged() {}
 
         /**
          * Triggered when the notification panel is starting or has finished
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 1c88289..8cc7e7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -37,11 +37,12 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 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.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -64,6 +65,7 @@
     private final Context mContext;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
+    private final SelectedUserInteractor mUserInteractor;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new UpdateMonitorCallback();
     private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
@@ -83,7 +85,7 @@
     private boolean mTrustManaged;
     private boolean mTrusted;
     private boolean mDebugUnlocked = false;
-    private boolean mFaceAuthEnabled;
+    private boolean mFaceEnrolled;
 
     private float mDismissAmount = 0f;
     private boolean mDismissingFromTouch = false;
@@ -120,11 +122,13 @@
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             KeyguardUpdateMonitorLogger logger,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor userInteractor) {
         mContext = context;
         mLogger = logger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
+        mUserInteractor = userInteractor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
         mFeatureFlags = featureFlags;
@@ -212,7 +216,7 @@
 
     private void notifyKeyguardFaceAuthEnabledChanged() {
         // Copy the list to allow removal during callback.
-        new ArrayList<>(mCallbacks).forEach(Callback::onFaceAuthEnabledChanged);
+        new ArrayList<>(mCallbacks).forEach(Callback::onFaceEnrolledChanged);
     }
 
     private void notifyUnlockedChanged() {
@@ -250,22 +254,22 @@
     @VisibleForTesting
     void update(boolean updateAlways) {
         Trace.beginSection("KeyguardStateController#update");
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mUserInteractor.getSelectedUserId();
         boolean secure = mLockPatternUtils.isSecure(user);
         boolean canDismissLockScreen = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)
                 || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
         boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
         boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
-        boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
+        boolean faceEnrolled = mKeyguardUpdateMonitor.isFaceEnrolled(user);
         boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
                 || trustManaged != mTrustManaged || mTrusted != trusted
-                || mFaceAuthEnabled != faceAuthEnabled;
+                || mFaceEnrolled != faceEnrolled;
         if (changed || updateAlways) {
             mSecure = secure;
             mCanDismissLockScreen = canDismissLockScreen;
             mTrusted = trusted;
             mTrustManaged = trustManaged;
-            mFaceAuthEnabled = faceAuthEnabled;
+            mFaceEnrolled = faceEnrolled;
             mLogger.logKeyguardStateUpdate(
                     mSecure, mCanDismissLockScreen, mTrusted, mTrustManaged);
             notifyUnlockedChanged();
@@ -286,8 +290,8 @@
     }
 
     @Override
-    public boolean isFaceAuthEnabled() {
-        return mFaceAuthEnabled;
+    public boolean isFaceEnrolled() {
+        return mFaceEnrolled;
     }
 
     @Override
@@ -412,7 +416,7 @@
         pw.println("  mTrustManaged: " + mTrustManaged);
         pw.println("  mTrusted: " + mTrusted);
         pw.println("  mDebugUnlocked: " + mDebugUnlocked);
-        pw.println("  mFaceAuthEnabled: " + mFaceAuthEnabled);
+        pw.println("  mFaceEnrolled: " + mFaceEnrolled);
         pw.println("  isKeyguardFadingAway: " + isKeyguardFadingAway());
         pw.println("  isKeyguardGoingAway: " + isKeyguardGoingAway());
         pw.println("  isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index bb074ac..dfe2686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -38,10 +38,11 @@
 import com.android.keyguard.KeyguardVisibilityHelper;
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -160,6 +161,7 @@
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            FeatureFlags featureFlags,
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardUserSwitcherView);
@@ -174,7 +176,8 @@
                 mUserSwitcherController, this);
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
-                screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+                screenOffAnimationController, /* animateYPos= */ false,
+                featureFlags, /* logBuffer= */ null);
         mBackground = new KeyguardUserSwitcherScrim(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 53fed3d..ceed81a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -305,7 +305,8 @@
                             && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
                     if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
                         // Pass null to ensure all inputs are cleared for this entry b/227115380
-                        mController.removeRemoteInput(mEntry, null);
+                            mController.removeRemoteInput(mEntry, null,
+                                    /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
                     }
                 }
             }
@@ -426,7 +427,7 @@
 
     @VisibleForTesting
     void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
-        mController.removeRemoteInput(mEntry, mToken);
+        mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus");
         mEntry.remoteInputText = mEditText.getText();
 
         // During removal, we get reattached and lose focus. Not hiding in that
@@ -536,7 +537,8 @@
         if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
             return;
         }
-        mController.removeRemoteInput(mEntry, mToken);
+        mController.removeRemoteInput(mEntry, mToken,
+                /* reason= */"RemoteInputView#onDetachedFromWindow");
         mController.removeSpinning(mEntry.getKey(), mToken);
     }
 
@@ -655,7 +657,7 @@
         mEditText.setText(mEntry.remoteInputText);
         mEditText.setSelection(mEditText.length());
         mEditText.requestFocus();
-        mController.addRemoteInput(mEntry, mToken);
+        mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus");
         setAttachment(mEntry.remoteInputAttachment);
 
         updateSendButton();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index a50fd6f..6c0d433 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -255,7 +255,8 @@
         entry.lastRemoteInputSent = SystemClock.elapsedRealtime()
         entry.mRemoteEditImeAnimatingAway = true
         remoteInputController.addSpinning(entry.key, view.mToken)
-        remoteInputController.removeRemoteInput(entry, view.mToken)
+        remoteInputController.removeRemoteInput(entry, view.mToken,
+               /* reason= */ "RemoteInputViewController#sendRemoteInput")
         remoteInputController.remoteInputSent(entry)
         entry.setHasSentReply()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index f88339a..7829d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import dagger.Lazy
 import java.io.PrintWriter
@@ -41,7 +41,7 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val userSwitcherInteractorLazy: Lazy<UserSwitcherInteractor>,
     private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
     private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
     private val activityStarter: ActivityStarter,
@@ -53,26 +53,29 @@
         fun onUserSwitched()
     }
 
-    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val mUserSwitcherInteractor: UserSwitcherInteractor by lazy {
+        userSwitcherInteractorLazy.get()
+    }
     private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
     private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
 
-    private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
+    private val callbackCompatMap =
+        mutableMapOf<UserSwitchCallback, UserSwitcherInteractor.UserCallback>()
 
     /** The current list of [UserRecord]. */
     val users: ArrayList<UserRecord>
-        get() = userInteractor.userRecords.value
+        get() = mUserSwitcherInteractor.userRecords.value
 
     /** Whether the user switcher experience should use the simple experience. */
     val isSimpleUserSwitcher: Boolean
-        get() = userInteractor.isSimpleUserSwitcher
+        get() = mUserSwitcherInteractor.isSimpleUserSwitcher
 
     val isUserSwitcherEnabled: Boolean
-        get() = userInteractor.isUserSwitcherEnabled
+        get() = mUserSwitcherInteractor.isUserSwitcherEnabled
 
     /** The [UserRecord] of the current user or `null` when none. */
     val currentUserRecord: UserRecord?
-        get() = userInteractor.selectedUserRecord.value
+        get() = mUserSwitcherInteractor.selectedUserRecord.value
 
     /** The name of the current user of the device or `null`, when none is selected. */
     val currentUserName: String?
@@ -81,8 +84,8 @@
                 LegacyUserUiHelper.getUserRecordName(
                     context = applicationContext,
                     record = it,
-                    isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                    isGuestUserResetting = userInteractor.isGuestUserResetting,
+                    isGuestUserAutoCreated = mUserSwitcherInteractor.isGuestUserAutoCreated,
+                    isGuestUserResetting = mUserSwitcherInteractor.isGuestUserResetting,
                 )
             }
 
@@ -98,21 +101,21 @@
      * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
      */
     fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
-        userInteractor.selectUser(userId, dialogShower)
+        mUserSwitcherInteractor.selectUser(userId, dialogShower)
     }
 
     /** Whether the guest user is configured to always be present on the device. */
     val isGuestUserAutoCreated: Boolean
-        get() = userInteractor.isGuestUserAutoCreated
+        get() = mUserSwitcherInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean
-        get() = userInteractor.isGuestUserResetting
+        get() = mUserSwitcherInteractor.isGuestUserResetting
 
     /** Registers an adapter to notify when the users change. */
     fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
-        userInteractor.addCallback(
-            object : UserInteractor.UserCallback {
+        mUserSwitcherInteractor.addCallback(
+            object : UserSwitcherInteractor.UserCallback {
                 override fun isEvictable(): Boolean {
                     return adapter.get() == null
                 }
@@ -129,7 +132,7 @@
         record: UserRecord,
         dialogShower: DialogShower?,
     ) {
-        userInteractor.onRecordSelected(record, dialogShower)
+        mUserSwitcherInteractor.onRecordSelected(record, dialogShower)
     }
 
     /**
@@ -152,7 +155,7 @@
      *   `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
      */
     fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
-        userInteractor.removeGuestUser(
+        mUserSwitcherInteractor.removeGuestUser(
             guestUserId = guestUserId,
             targetUserId = targetUserId,
         )
@@ -168,7 +171,7 @@
      *   only if its ephemeral, else keep guest
      */
     fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
-        userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+        mUserSwitcherInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
     }
 
     /**
@@ -194,31 +197,31 @@
      * The pictures are only loaded if they have not been loaded yet.
      */
     fun refreshUsers() {
-        userInteractor.refreshUsers()
+        mUserSwitcherInteractor.refreshUsers()
     }
 
     /** Adds a subscriber to when user switches. */
     fun addUserSwitchCallback(callback: UserSwitchCallback) {
         val interactorCallback =
-            object : UserInteractor.UserCallback {
+            object : UserSwitcherInteractor.UserCallback {
                 override fun onUserStateChanged() {
                     callback.onUserSwitched()
                 }
             }
         callbackCompatMap[callback] = interactorCallback
-        userInteractor.addCallback(interactorCallback)
+        mUserSwitcherInteractor.addCallback(interactorCallback)
     }
 
     /** Removes a previously-added subscriber. */
     fun removeUserSwitchCallback(callback: UserSwitchCallback) {
         val interactorCallback = callbackCompatMap.remove(callback)
         if (interactorCallback != null) {
-            userInteractor.removeCallback(interactorCallback)
+            mUserSwitcherInteractor.removeCallback(interactorCallback)
         }
     }
 
     fun dump(pw: PrintWriter, args: Array<out String>) {
-        userInteractor.dump(pw)
+        mUserSwitcherInteractor.dump(pw)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index eaae0f0..fa6ea4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -30,10 +30,13 @@
 import android.util.Log
 import android.view.View.MeasureSpec
 import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.Dependency
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.time.SystemClock
 import java.text.FieldPosition
@@ -83,7 +86,7 @@
 class VariableDateViewController(
     private val systemClock: SystemClock,
     private val broadcastDispatcher: BroadcastDispatcher,
-    private val shadeExpansionStateManager: ShadeExpansionStateManager,
+    private val shadeInteractor: ShadeInteractor,
     private val shadeLogger: ShadeLogger,
     private val timeTickHandler: Handler,
     view: VariableDateView
@@ -174,8 +177,11 @@
 
         broadcastDispatcher.registerReceiver(intentReceiver, filter,
                 HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
-
-        shadeExpansionStateManager.addQsExpansionFractionListener(::onQsExpansionFractionChanged)
+        mView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                shadeInteractor.qsExpansion.collect(::onQsExpansionFractionChanged)
+            }
+        }
         post(::updateClock)
         mView.onAttach(onMeasureListener)
     }
@@ -183,7 +189,6 @@
     override fun onViewDetached() {
         dateFormat = null
         mView.onAttach(null)
-        shadeExpansionStateManager.removeQsExpansionFractionListener(::onQsExpansionFractionChanged)
         broadcastDispatcher.unregisterReceiver(intentReceiver)
     }
 
@@ -237,18 +242,18 @@
     class Factory @Inject constructor(
         private val systemClock: SystemClock,
         private val broadcastDispatcher: BroadcastDispatcher,
-        private val shadeExpansionStateManager: ShadeExpansionStateManager,
+        private val shadeInteractor: ShadeInteractor,
         private val shadeLogger: ShadeLogger,
         @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
     ) {
         fun create(view: VariableDateView): VariableDateViewController {
             return VariableDateViewController(
-                    systemClock,
-                    broadcastDispatcher,
-                    shadeExpansionStateManager,
-                    shadeLogger,
-                    handler,
-                    view
+                systemClock,
+                broadcastDispatcher,
+                shadeInteractor,
+                shadeLogger,
+                handler,
+                view
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
index 80f3d76..96717c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
@@ -38,7 +38,8 @@
     /**
      * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once
      * those statuses have been fetched. The fetching occurs on a background thread because IPCs may
-     * be required to fetch the statuses (see b/271058380).
+     * be required to fetch the statuses (see b/271058380). However, the callback will be invoked in
+     * the main thread.
      */
     fun fetchConnectionStatusInBackground(
         currentDevices: Collection<CachedBluetoothDevice>,
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 9269df3..8c66c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -19,6 +19,7 @@
 import android.content.ContentResolver
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
+import android.os.Trace
 import android.util.Log
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.SysUISingleton
@@ -57,6 +58,7 @@
     private var folded: Boolean? = null
     private var isTransitionEnabled: Boolean? = null
     private val foldStateListener = FoldStateListener(context)
+    private var unfoldInProgress = false
     private val isFoldable: Boolean
         get() =
             context.resources
@@ -95,7 +97,7 @@
         // the unfold animation (e.g. it could be disabled because of battery saver).
         // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
         if (folded == false && isTransitionEnabled == false) {
-            latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+            onUnfoldEnded()
 
             if (DEBUG) {
                 Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
@@ -116,7 +118,7 @@
         }
 
         if (folded == false && isTransitionEnabled == true) {
-            latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+            onUnfoldEnded()
 
             if (DEBUG) {
                 Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
@@ -124,6 +126,22 @@
         }
     }
 
+    private fun onUnfoldStarted() {
+        if (unfoldInProgress) return
+        unfoldInProgress = true
+        // As LatencyTracker might be disabled, let's also log a parallel slice to the trace to be
+        // able to debug all cases.
+        latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, UNFOLD_IN_PROGRESS_TRACE_NAME, /* cookie= */ 0)
+    }
+
+    private fun onUnfoldEnded() {
+        if (!unfoldInProgress) return
+        unfoldInProgress = false
+        latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+        Trace.endAsyncSection(UNFOLD_IN_PROGRESS_TRACE_NAME, 0)
+    }
+
     private fun onFoldEvent(folded: Boolean) {
         val oldFolded = this.folded
 
@@ -139,7 +157,7 @@
             // unfolding the device.
             if (oldFolded != null && !folded) {
                 // Unfolding started
-                latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+                onUnfoldStarted()
                 isTransitionEnabled =
                     transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
 
@@ -159,4 +177,5 @@
 }
 
 private const val TAG = "UnfoldLatencyTracker"
+private const val UNFOLD_IN_PROGRESS_TRACE_NAME = "Switch displays during unfold"
 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
new file mode 100644
index 0000000..ed960f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.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.unfold
+
+import android.content.Context
+import android.os.Trace
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.unfold.system.DeviceStateRepository
+import com.android.systemui.unfold.updates.FoldStateRepository
+import com.android.systemui.util.TraceStateLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Logs several unfold related details in a trace. Mainly used for debugging and investigate
+ * droidfooders traces.
+ */
+@SysUISingleton
+class UnfoldTraceLogger
+@Inject
+constructor(
+    private val context: Context,
+    private val foldStateRepository: FoldStateRepository,
+    @Application private val applicationScope: CoroutineScope,
+    private val deviceStateRepository: DeviceStateRepository
+) : CoreStartable {
+    private val isFoldable: Boolean
+        get() =
+            context.resources
+                .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
+                .isNotEmpty()
+
+    override fun start() {
+        if (!isFoldable) return
+
+        applicationScope.launch {
+            val foldUpdateLogger = TraceStateLogger("FoldUpdate")
+            foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) }
+        }
+
+        applicationScope.launch {
+            foldStateRepository.hingeAngle.collect {
+                Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt())
+            }
+        }
+        applicationScope.launch {
+            val foldedStateLogger = TraceStateLogger("FoldedState")
+            deviceStateRepository.isFolded.collect { isFolded ->
+                foldedStateLogger.log(
+                    if (isFolded) {
+                        "folded"
+                    } else {
+                        "unfolded"
+                    }
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index ed3eacd..71314f1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
 import android.os.SystemProperties
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.LifecycleScreenStatusProvider
@@ -34,16 +35,26 @@
 import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
 import com.android.systemui.util.time.SystemClockImpl
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import dagger.Binds
 import dagger.Lazy
 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.Named
 import javax.inject.Provider
 import javax.inject.Singleton
 
-@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
+@Module(
+    includes =
+        [
+            UnfoldSharedModule::class,
+            SystemUnfoldSharedModule::class,
+            UnfoldTransitionModule.Bindings::class
+        ]
+)
 class UnfoldTransitionModule {
 
     @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@@ -136,13 +147,22 @@
                 null
             }
 
-        return resultingProvider?.get()?.orElse(null)?.let {
-            unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
-        } ?: ShellUnfoldProgressProvider.NO_PROVIDER
+        return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider ->
+            UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
+        }
+            ?: ShellUnfoldProgressProvider.NO_PROVIDER
     }
 
     @Provides
     fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
+
+    @Module
+    interface Bindings {
+        @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 f404549..5fc435a 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
@@ -23,7 +23,6 @@
 import android.os.UserManager
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.res.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -31,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
@@ -132,7 +132,6 @@
                         Settings.Global.ADD_USERS_WHEN_LOCKED,
                         Settings.Global.USER_SWITCHER_ENABLED,
                     ),
-                userId = UserHandle.USER_SYSTEM,
             )
             .onStart { emit(Unit) } // Forces an initial update.
             .map { getSettings() }
@@ -247,7 +246,7 @@
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
-                globalSettings.getIntForUser(
+                globalSettings.getInt(
                     SETTING_SIMPLE_USER_SWITCHER,
                     if (
                         appContext.resources.getBoolean(
@@ -258,18 +257,16 @@
                     } else {
                         0
                     },
-                    UserHandle.USER_SYSTEM,
                 ) != 0
 
             val isAddUsersFromLockscreen =
-                globalSettings.getIntForUser(
+                globalSettings.getInt(
                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                     0,
-                    UserHandle.USER_SYSTEM,
                 ) != 0
 
             val isUserSwitcherEnabled =
-                globalSettings.getIntForUser(
+                globalSettings.getInt(
                     Settings.Global.USER_SWITCHER_ENABLED,
                     if (
                         appContext.resources.getBoolean(
@@ -280,7 +277,6 @@
                     } else {
                         0
                     },
-                    UserHandle.USER_SYSTEM,
                 ) != 0
 
             UserSwitcherSettingsModel(
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index dc7fadd..12387893 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -21,7 +21,6 @@
 import android.os.Handler
 import android.os.UserManager
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -30,7 +29,6 @@
 import com.android.systemui.qs.SettingObserver
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.util.settings.GlobalSettings
@@ -61,10 +59,10 @@
     @Background private val bgHandler: Handler,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val userManager: UserManager,
-    private val userTracker: UserTracker,
     private val userSwitcherController: UserSwitcherController,
     private val userInfoController: UserInfoController,
     private val globalSetting: GlobalSettings,
+    private val userRepository: UserRepository,
 ) : UserSwitcherRepository {
     private val showUserSwitcherForSingleUser =
         context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
@@ -80,7 +78,7 @@
                     globalSetting,
                     bgHandler,
                     USER_SWITCHER_ENABLED,
-                    userTracker.userId,
+                    userRepository.getSelectedUserInfo().id,
                 ) {
                 override fun handleValueChanged(value: Int, observedChange: Boolean) {
                     if (observedChange) {
@@ -147,7 +145,7 @@
 
     private suspend fun isGuestUser(): Boolean {
         return withContext(bgDispatcher) {
-            userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+            userManager.isGuestUser(userRepository.getSelectedUserInfo().id)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
new file mode 100644
index 0000000..0e693d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.user.domain.interactor
+
+import android.annotation.UserIdInt
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+
+/** Encapsulates business logic to interact the selected user */
+@SysUISingleton
+class SelectedUserInteractor
+@Inject
+constructor(
+    private val repository: UserRepository,
+    private val flags: FeatureFlagsClassic,
+) {
+
+    /**
+     * Returns the ID of the currently-selected user.
+     *
+     * @param bypassFlag this will ignore the feature flag and get the data from the repository
+     *   instead. This is used for refactored methods that were previously pointing to `userTracker`
+     *   and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled.
+     *   KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together
+     *   with this flag).
+     */
+    @UserIdInt
+    @JvmOverloads
+    fun getSelectedUserId(bypassFlag: Boolean = false): Int {
+        if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+            return repository.getSelectedUserInfo().id
+        } else {
+            return KeyguardUpdateMonitor.getCurrentUser()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index dbc3bf3..e0d205f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -83,9 +83,9 @@
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 
-/** Encapsulates business logic to interact with user data and systems. */
+/** Encapsulates business logic to for the user switcher. */
 @SysUISingleton
-class UserInteractor
+class UserSwitcherInteractor
 @Inject
 constructor(
     @Application private val applicationContext: Context,
@@ -383,10 +383,6 @@
         pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated")
     }
 
-    fun onDeviceBootCompleted() {
-        guestUserInteractor.onDeviceBootCompleted()
-    }
-
     /** Switches to the user or executes the action represented by the given record. */
     fun onRecordSelected(
         record: UserRecord,
@@ -535,12 +531,6 @@
         }
     }
 
-    /** Returns the ID of the currently-selected user. */
-    @UserIdInt
-    fun getSelectedUserId(): Int {
-        return repository.getSelectedUserInfo().id
-    }
-
     private fun showDialog(request: ShowDialogRequestModel) {
         _dialogShowRequests.value = request
     }
@@ -664,7 +654,6 @@
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
-
         if (userId != Process.myUserHandle().identifier) {
             applicationContext.startServiceAsUser(
                 intent,
@@ -826,6 +815,6 @@
     }
 
     companion object {
-        private const val TAG = "UserInteractor"
+        private const val TAG = "UserSwitcherInteractor"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 0930cb8..922dc05 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.user.UserSwitchFullscreenDialog
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import dagger.Lazy
@@ -53,7 +53,7 @@
     private val falsingManager: Lazy<FalsingManager>,
     private val broadcastSender: Lazy<BroadcastSender>,
     private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
-    private val interactor: Lazy<UserInteractor>,
+    private val interactor: Lazy<UserSwitcherInteractor>,
     private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
     private val eventLogger: Lazy<UiEventLogger>,
     private val activityStarter: Lazy<ActivityStarter>,
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 78edad7..2c425b19 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -17,12 +17,10 @@
 
 package com.android.systemui.user.ui.viewmodel
 
-import android.content.Context
 import android.graphics.drawable.Drawable
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -33,8 +31,7 @@
 class StatusBarUserChipViewModel
 @Inject
 constructor(
-    @Application private val context: Context,
-    interactor: UserInteractor,
+    interactor: UserSwitcherInteractor,
 ) {
     /** Whether the status bar chip ui should be available */
     val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 20f0fa8c..4089889 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,9 +20,8 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.drawable.CircularDrawable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
@@ -38,17 +37,17 @@
 class UserSwitcherViewModel
 @Inject
 constructor(
-    private val userInteractor: UserInteractor,
+    private val userSwitcherInteractor: UserSwitcherInteractor,
     private val guestUserInteractor: GuestUserInteractor,
 ) {
 
     /** The currently selected user. */
     val selectedUser: Flow<UserViewModel> =
-        userInteractor.selectedUser.map { user -> toViewModel(user) }
+        userSwitcherInteractor.selectedUser.map { user -> toViewModel(user) }
 
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
-        userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
+        userSwitcherInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
 
     /** The maximum number of columns that the user selection grid should use. */
     val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) }
@@ -61,7 +60,9 @@
     val isMenuVisible: Flow<Boolean> = _isMenuVisible
     /** The user action menu. */
     val menu: Flow<List<UserActionViewModel>> =
-        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
+        userSwitcherInteractor.actions.map { actions ->
+            actions.map { action -> toViewModel(action) }
+        }
 
     /** Whether the button to open the user action menu is visible. */
     val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
@@ -175,7 +176,7 @@
                     isTablet = true,
                 ),
             onClicked = {
-                userInteractor.executeAction(action = model)
+                userSwitcherInteractor.executeAction(action = model)
                 // We don't finish because we want to show a dialog over the full-screen UI and
                 // that dialog can be dismissed in case the user changes their mind and decides not
                 // to add a user.
@@ -195,7 +196,7 @@
             null
         } else {
             {
-                userInteractor.selectUser(model.id)
+                userSwitcherInteractor.selectUser(model.id)
                 userSwitched.value = true
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 760fe6a..f5edb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -42,6 +42,7 @@
      *   list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be
      *   used; try using `synchronized` or making a copy of the list instead.
      */
+    @Deprecated
     public static <T> void safeForeach(List<T> list, Consumer<T> c) {
         for (int i = list.size() - 1; i >= 0; i--) {
             T item = list.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java
new file mode 100644
index 0000000..855bba6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Descriptive annotation for clearly tagging callback types that are weakly
+ * referenced during registration.
+ *
+ * This is useful in providing hints to Proguard about certain fields that
+ * should be kept to preserve strong references.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({TYPE})
+public @interface WeaklyReferencedCallback {}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 0a44bda..f0c7be6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -4,27 +4,74 @@
 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.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.TraceUtils.Companion.coroutineTracingIsEnabled
+import com.android.systemui.util.tracing.TraceContextElement
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Qualifier
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/** Key associated with a [Boolean] flag that enables or disables the coroutine tracing feature. */
+@Qualifier
+annotation class CoroutineTracingEnabledKey
+
+/**
+ * Same as [@Application], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedApplication
+
+/**
+ * Same as [@Background], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedBackground
 
 /** Providers for various coroutines-related constructs. */
 @Module
-object CoroutinesModule {
+class CoroutinesModule {
     @Provides
     @SysUISingleton
     @Application
     fun applicationScope(
-        @Main dispatcher: CoroutineDispatcher,
-    ): CoroutineScope = CoroutineScope(dispatcher)
+            @Main dispatcherContext: CoroutineContext,
+    ): CoroutineScope = CoroutineScope(dispatcherContext)
+
+    @Provides
+    @SysUISingleton
+    @UnflaggedApplication
+    fun unflaggedApplicationScope(): CoroutineScope = CoroutineScope(Dispatchers.Main.immediate)
 
     @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.
@@ -37,5 +84,42 @@
     @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
+    }
+
+    @Provides
+    @UnflaggedBackground
+    @SysUISingleton
+    fun unflaggedBackgroundCoroutineContext(): CoroutineContext {
+        return Dispatchers.IO
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Provides
+    @Tracing
+    @SysUISingleton
+    fun tracingCoroutineContext(
+        @CoroutineTracingEnabledKey enableTracing: Boolean
+    ): CoroutineContext = if (enableTracing) TraceContextElement() else EmptyCoroutineContext
+
+    companion object {
+        @[Provides CoroutineTracingEnabledKey]
+        fun provideIsCoroutineTracingEnabledKey(featureFlags: FeatureFlagsClassic): Boolean {
+            return if (featureFlags.isEnabled(Flags.COROUTINE_TRACING)) {
+                coroutineTracingIsEnabled = true
+                true
+            } else false
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
new file mode 100644
index 0000000..909a18be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.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.util.kotlin
+
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.awaitCancellation
+
+/**
+ * Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for
+ * garbage collection.
+ *
+ * This utility is useful if you want to bind a [repeatWhenAttached] invocation to the lifetime of a
+ * coroutine, such that cancelling the coroutine cleans up the handle. For example:
+ * ```
+ * myFlow.collectLatest { value ->
+ *     val disposableHandle = myView.repeatWhenAttached { doStuff() }
+ *     doSomethingWith(value)
+ *     // un-bind when done
+ *     disposableHandle.awaitCancellationThenDispose()
+ * }
+ * ```
+ */
+suspend fun DisposableHandle.awaitCancellationThenDispose() {
+    try {
+        awaitCancellation()
+    } finally {
+        dispose()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 83ff789..8fe57e11 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -61,10 +61,10 @@
  *
  * Useful for code that needs to compare the current value to the previous value.
  */
-fun <T, R> Flow<T>.pairwiseBy(
-    initialValue: T,
-    transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(initialValue) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+    initialValue: S,
+    transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = pairwiseBy(getInitialValue = { initialValue }, transform)
 
 /**
  * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -75,10 +75,16 @@
  *
  * Useful for code that needs to compare the current value to the previous value.
  */
-fun <T, R> Flow<T>.pairwiseBy(
-    getInitialValue: suspend () -> T,
-    transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+    getInitialValue: suspend () -> S,
+    transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = flow {
+    var previousValue: S = getInitialValue()
+    collect { newVal ->
+        emit(transform(previousValue, newVal))
+        previousValue = newVal
+    }
+}
 
 /**
  * Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
@@ -86,7 +92,7 @@
  *
  * Useful for code that needs to compare the current value to the previous value.
  */
-fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
+fun <T> Flow<T>.pairwise(): Flow<WithPrev<T, T>> = pairwiseBy(::WithPrev)
 
 /**
  * Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue] will
@@ -94,10 +100,11 @@
  *
  * Useful for code that needs to compare the current value to the previous value.
  */
-fun <T> Flow<T>.pairwise(initialValue: T): Flow<WithPrev<T>> = pairwiseBy(initialValue, ::WithPrev)
+fun <S, T : S> Flow<T>.pairwise(initialValue: S): Flow<WithPrev<S, T>> =
+    pairwiseBy(initialValue, ::WithPrev)
 
 /** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
-data class WithPrev<T>(val previousValue: T, val newValue: T)
+data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
 
 /**
  * Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
@@ -265,7 +272,127 @@
  * immediately invoke [getValue] to establish its initial value.
  */
 inline fun <T> CoroutineScope.stateFlow(
-    changedSignals: Flow<Unit>,
+    changedSignals: Flow<*>,
     crossinline getValue: () -> T,
 ): StateFlow<T> =
     changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
+
+inline fun <T1, T2, T3, T4, T5, T6, R> combine(
+    flow: Flow<T1>,
+    flow2: Flow<T2>,
+    flow3: Flow<T3>,
+    flow4: Flow<T4>,
+    flow5: Flow<T5>,
+    flow6: Flow<T6>,
+    crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
+        ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+            args[0] as T1,
+            args[1] as T2,
+            args[2] as T3,
+            args[3] as T4,
+            args[4] as T5,
+            args[5] as T6
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+    flow: Flow<T1>,
+    flow2: Flow<T2>,
+    flow3: Flow<T3>,
+    flow4: Flow<T4>,
+    flow5: Flow<T5>,
+    flow6: Flow<T6>,
+    flow7: Flow<T7>,
+    crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+            args[0] as T1,
+            args[1] as T2,
+            args[2] as T3,
+            args[3] as T4,
+            args[4] as T5,
+            args[5] as T6,
+            args[6] as T7
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+    flow: Flow<T1>,
+    flow2: Flow<T2>,
+    flow3: Flow<T3>,
+    flow4: Flow<T4>,
+    flow5: Flow<T5>,
+    flow6: Flow<T6>,
+    flow7: Flow<T7>,
+    flow8: Flow<T8>,
+    crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+            args[0] as T1,
+            args[1] as T2,
+            args[2] as T3,
+            args[3] as T4,
+            args[4] as T5,
+            args[5] as T6,
+            args[6] as T7,
+            args[7] as T8
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
+    flow: Flow<T1>,
+    flow2: Flow<T2>,
+    flow3: Flow<T3>,
+    flow4: Flow<T4>,
+    flow5: Flow<T5>,
+    flow6: Flow<T6>,
+    flow7: Flow<T7>,
+    flow8: Flow<T8>,
+    flow9: Flow<T9>,
+    crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(
+        flow,
+        flow2,
+        flow3,
+        flow4,
+        flow5,
+        flow6,
+        flow7,
+        flow8,
+        flow9
+    ) { args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+            args[0] as T1,
+            args[1] as T2,
+            args[2] as T3,
+            args[3] as T4,
+            args[4] as T5,
+            args[5] as T6,
+            args[6] as T7,
+            args[6] as T8,
+            args[6] as T9,
+        )
+    }
+}
+
+/**
+ * Returns a [Flow] that immediately emits [Unit] when started, then emits from the given upstream
+ * [Flow] as normal.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun Flow<Unit>.emitOnStart(): Flow<Unit> = onStart { emit(Unit) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
new file mode 100644
index 0000000..41cd95b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.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.util.kotlin
+
+/** Like [mapValues], but discards `null` values returned from [block]. */
+fun <K, V, R> Map<K, V>.mapValuesNotNull(block: (Map.Entry<K, V>) -> R?): Map<K, R> = buildMap {
+    this@mapValuesNotNull.mapValuesNotNullTo(this, block)
+}
+
+/** Like [mapValuesTo], but discards `null` values returned from [block]. */
+fun <K, V, R, M : MutableMap<in K, in R>> Map<out K, V>.mapValuesNotNullTo(
+    destination: M,
+    block: (Map.Entry<K, V>) -> R?,
+): M {
+    for (entry in this) {
+        block(entry)?.also { destination.put(entry.key, it) }
+    }
+    return destination
+}
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 73e2f97..ffbc10a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -19,6 +19,7 @@
 class Utils {
     companion object {
         fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
+        fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
 
         fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d)
         fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) =
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 9b06a37..d566725 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -191,7 +191,7 @@
     }
 
     @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         throw new UnsupportedOperationException("not implemented");
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 968dcc9..df5162a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,6 +26,7 @@
 
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -64,6 +65,7 @@
      * An interface for listening to the connection status.
      * @param <T> The wrapper type.
      */
+    @WeaklyReferencedCallback
     public interface Callback<T> {
         /**
          * Invoked when the service has been successfully connected to.
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
index 7687432..425336d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.util.service;
 
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+
 /**
  * The {@link Observer} interface specifies an entity which listeners
  * can be informed of changes to the source, which will require updating. Note that this deals
@@ -25,6 +27,7 @@
     /**
      * Callback for receiving updates from the {@link Observer}.
      */
+    @WeaklyReferencedCallback
     interface Callback {
         /**
          * Invoked when the source has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 85fada2..42389f0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -16,22 +16,23 @@
 
 package com.android.systemui.util.settings;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.settings.UserTracker;
-
 import javax.inject.Inject;
 
+// use UserHandle.USER_SYSTEM everywhere
+@SuppressLint("StaticSettingsProvider")
 class GlobalSettingsImpl implements GlobalSettings {
     private final ContentResolver mContentResolver;
-    private final UserTracker mUserTracker;
 
     @Inject
-    GlobalSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
+    GlobalSettingsImpl(ContentResolver contentResolver) {
         mContentResolver = contentResolver;
-        mUserTracker = userTracker;
     }
 
     @Override
@@ -40,43 +41,23 @@
     }
 
     @Override
-    public UserTracker getUserTracker() {
-        return mUserTracker;
-    }
-
-    @Override
     public Uri getUriFor(String name) {
         return Settings.Global.getUriFor(name);
     }
 
     @Override
-    public String getStringForUser(String name, int userHandle) {
-        return Settings.Global.getStringForUser(mContentResolver, name,
-                getRealUserHandle(userHandle));
+    public String getString(String name) {
+        return Settings.Global.getString(mContentResolver, name);
     }
 
     @Override
-    public boolean putString(String name, String value, boolean overrideableByRestore) {
-        throw new UnsupportedOperationException(
-                "This method only exists publicly for Settings.System and Settings.Secure");
+    public boolean putString(String name, String value) {
+        return Settings.Global.putString(mContentResolver, name, value);
     }
 
     @Override
-    public boolean putStringForUser(String name, String value, int userHandle) {
-        return Settings.Global.putStringForUser(mContentResolver, name, value,
-                getRealUserHandle(userHandle));
-    }
-
-    @Override
-    public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
-            int userHandle, boolean overrideableByRestore) {
-        return Settings.Global.putStringForUser(
-                mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
-                overrideableByRestore);
-    }
-
-    @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault) {
         return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
index 798033e..6031a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
@@ -22,5 +22,5 @@
  * See {@link SettingsProxy} for details.
  */
 
-public interface SecureSettings extends SettingsProxy {
+public interface SecureSettings extends UserSettingsProxy {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index f995436..6532ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -20,6 +20,8 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
@@ -75,7 +77,7 @@
     }
 
     @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
         return Settings.Secure.putString(mContentResolver, name, value, tag, makeDefault);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
index b6846a3..6a9edc1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
@@ -18,25 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
-import android.os.UserHandle;
 import android.provider.Settings;
 
-import com.android.systemui.settings.UserTracker;
-
 /**
- * Used to interact with Settings.Secure, Settings.Global, and Settings.System.
- *
+ * Used to interact with mainly with Settings.Global, but can also be used for Settings.System
+ * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy}
+ * must be used instead.
+ * <p>
  * This interface can be implemented to give instance method (instead of static method) versions
- * of Settings.Secure, Settings.Global, and Settings.System. It can be injected into class
- * constructors and then faked or mocked as needed in tests.
- *
- * You can ask for {@link SecureSettings}, {@link GlobalSettings}, or {@link SystemSettings} to be
- * injected as needed.
- *
+ * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed
+ * in tests.
+ * <p>
+ * You can ask for {@link GlobalSettings} to be injected as needed.
+ * <p>
  * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
  * normally found on {@link ContentResolver} instances, unifying setting related actions in one
  * place.
@@ -49,29 +46,6 @@
     ContentResolver getContentResolver();
 
     /**
-     * Returns that {@link UserTracker} this instance was constructed with.
-     */
-    UserTracker getUserTracker();
-
-    /**
-     * Returns the user id for the associated {@link ContentResolver}.
-     */
-    default int getUserId() {
-        return getContentResolver().getUserId();
-    }
-
-    /**
-     * Returns the actual current user handle when querying with the current user. Otherwise,
-     * returns the passed in user id.
-     */
-    default int getRealUserHandle(int userHandle) {
-        if (userHandle != UserHandle.USER_CURRENT) {
-            return userHandle;
-        }
-        return getUserTracker().getUserId();
-    }
-
-    /**
      * Construct the content URI for a particular name/value pair,
      * useful for monitoring changes with a ContentObserver.
      * @param name to look up in the table
@@ -82,7 +56,7 @@
     /**
      * Convenience wrapper around
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     *
+     * <p>
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
     default void registerContentObserver(String name, ContentObserver settingsObserver) {
@@ -94,13 +68,13 @@
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
      */
     default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, settingsObserver, getUserId());
+        registerContentObserver(uri, false, settingsObserver);
     }
 
     /**
      * Convenience wrapper around
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     *
+     * <p>
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
     default void registerContentObserver(String name, boolean notifyForDescendants,
@@ -114,53 +88,8 @@
      */
     default void registerContentObserver(Uri uri, boolean notifyForDescendants,
             ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     *
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserverForUser(
-            String name, ContentObserver settingsObserver, int userHandle) {
-        registerContentObserverForUser(
-                getUriFor(name), settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     */
-    default void registerContentObserverForUser(
-            Uri uri, ContentObserver settingsObserver, int userHandle) {
-        registerContentObserverForUser(
-                uri, false, settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     *
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserverForUser(
-            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
-            int userHandle) {
-        registerContentObserverForUser(
-                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     */
-    default void registerContentObserverForUser(
-            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
-            int userHandle) {
         getContentResolver().registerContentObserver(
-                uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+                uri, notifyForDescendants, settingsObserver);
     }
 
     /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
@@ -173,22 +102,7 @@
      * @param name to look up in the table
      * @return the corresponding value, or null if not present
      */
-    default String getString(String name) {
-        return getStringForUser(name, getUserId());
-    }
-
-    /**See {@link #getString(String)}. */
-    String getStringForUser(String name, int userHandle);
-
-    /**
-     * Store a name/value pair into the database. Values written by this method will be
-     * overridden if a restore happens in the future.
-     *
-     * @param name to store
-     * @param value to associate with the name
-     * @return true if the value was set, false on database errors
-     */
-    boolean putString(String name, String value, boolean overrideableByRestore);
+    String getString(String name);
 
     /**
      * Store a name/value pair into the database.
@@ -196,16 +110,7 @@
      * @param value to associate with the name
      * @return true if the value was set, false on database errors
      */
-    default boolean putString(String name, String value) {
-        return putStringForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putString(String, String)}. */
-    boolean putStringForUser(String name, String value, int userHandle);
-
-    /** See {@link #putString(String, String)}. */
-    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
-            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+    boolean putString(String name, String value);
 
     /**
      * Store a name/value pair into the database.
@@ -262,12 +167,7 @@
      * or not a valid integer.
      */
     default int getInt(String name, int def) {
-        return getIntForUser(name, def, getUserId());
-    }
-
-    /** See {@link #getInt(String, int)}. */
-    default int getIntForUser(String name, int def, int userHandle) {
-        String v = getStringForUser(name, userHandle);
+        String v = getString(name);
         try {
             return v != null ? Integer.parseInt(v) : def;
         } catch (NumberFormatException e) {
@@ -292,14 +192,9 @@
      *
      * @return The setting's current value.
      */
-    default int getInt(String name) throws Settings.SettingNotFoundException {
-        return getIntForUser(name, getUserId());
-    }
-
-    /** See {@link #getInt(String)}. */
-    default int getIntForUser(String name, int userHandle)
+    default int getInt(String name)
             throws Settings.SettingNotFoundException {
-        String v = getStringForUser(name, userHandle);
+        String v = getString(name);
         try {
             return Integer.parseInt(v);
         } catch (NumberFormatException e) {
@@ -320,12 +215,7 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putInt(String name, int value) {
-        return putIntForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putInt(String, int)}. */
-    default boolean putIntForUser(String name, int value, int userHandle) {
-        return putStringForUser(name, Integer.toString(value), userHandle);
+        return putString(name, Integer.toString(value));
     }
 
     /**
@@ -342,12 +232,7 @@
      * or not a valid boolean.
      */
     default boolean getBool(String name, boolean def) {
-        return getBoolForUser(name, def, getUserId());
-    }
-
-    /** See {@link #getBool(String, boolean)}. */
-    default boolean getBoolForUser(String name, boolean def, int userHandle) {
-        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+        return getInt(name, def ? 1 : 0) != 0;
     }
 
     /**
@@ -367,14 +252,9 @@
      *
      * @return The setting's current value.
      */
-    default boolean getBool(String name) throws Settings.SettingNotFoundException {
-        return getBoolForUser(name, getUserId());
-    }
-
-    /** See {@link #getBool(String)}. */
-    default boolean getBoolForUser(String name, int userHandle)
+    default boolean getBool(String name)
             throws Settings.SettingNotFoundException {
-        return getIntForUser(name, userHandle) != 0;
+        return getInt(name) != 0;
     }
 
     /**
@@ -390,12 +270,7 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putBool(String name, boolean value) {
-        return putBoolForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putBool(String, boolean)}. */
-    default boolean putBoolForUser(String name, boolean value, int userHandle) {
-        return putIntForUser(name, value ? 1 : 0, userHandle);
+        return putInt(name, value ? 1 : 0);
     }
 
     /**
@@ -412,12 +287,12 @@
      * or not a valid {@code long}.
      */
     default long getLong(String name, long def) {
-        return getLongForUser(name, def, getUserId());
+        String valString = getString(name);
+        return parseLongOrUseDefault(valString, def);
     }
 
-    /** See {@link #getLong(String, long)}. */
-    default long getLongForUser(String name, long def, int userHandle) {
-        String valString = getStringForUser(name, userHandle);
+    /** Convert a string to a long, or uses a default if the string is malformed or null */
+    static long parseLongOrUseDefault(String valString, long def) {
         long value;
         try {
             value = valString != null ? Long.parseLong(valString) : def;
@@ -443,14 +318,15 @@
      * @throws Settings.SettingNotFoundException Thrown if a setting by the given
      * name can't be found or the setting value is not an integer.
      */
-    default long getLong(String name) throws Settings.SettingNotFoundException {
-        return getLongForUser(name, getUserId());
+    default long getLong(String name)
+            throws Settings.SettingNotFoundException {
+        String valString = getString(name);
+        return parseLongOrThrow(name, valString);
     }
 
-    /** See {@link #getLong(String)}. */
-    default long getLongForUser(String name, int userHandle)
+    /** Convert a string to a long, or throws an exception if the string is malformed or null */
+    static long parseLongOrThrow(String name, String valString)
             throws Settings.SettingNotFoundException {
-        String valString = getStringForUser(name, userHandle);
         try {
             return Long.parseLong(valString);
         } catch (NumberFormatException e) {
@@ -471,12 +347,7 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putLong(String name, long value) {
-        return putLongForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putLong(String, long)}. */
-    default boolean putLongForUser(String name, long value, int userHandle) {
-        return putStringForUser(name, Long.toString(value), userHandle);
+        return putString(name, Long.toString(value));
     }
 
     /**
@@ -493,12 +364,12 @@
      * or not a valid float.
      */
     default float getFloat(String name, float def) {
-        return getFloatForUser(name, def, getUserId());
+        String v = getString(name);
+        return parseFloat(v, def);
     }
 
-    /** See {@link #getFloat(String)}. */
-    default float getFloatForUser(String name, float def, int userHandle) {
-        String v = getStringForUser(name, userHandle);
+    /** Convert a string to a float, or uses a default if the string is malformed or null */
+    static float parseFloat(String v, float def) {
         try {
             return v != null ? Float.parseFloat(v) : def;
         } catch (NumberFormatException e) {
@@ -523,14 +394,15 @@
      *
      * @return The setting's current value.
      */
-    default float getFloat(String name) throws Settings.SettingNotFoundException {
-        return getFloatForUser(name, getUserId());
+    default float getFloat(String name)
+            throws Settings.SettingNotFoundException {
+        String v = getString(name);
+        return parseFloatOrThrow(name, v);
     }
 
-    /** See {@link #getFloat(String, float)}. */
-    default float getFloatForUser(String name, int userHandle)
+    /** Convert a string to a float, or throws an exception if the string is malformed or null */
+    static float parseFloatOrThrow(String name, String v)
             throws Settings.SettingNotFoundException {
-        String v = getStringForUser(name, userHandle);
         if (v == null) {
             throw new Settings.SettingNotFoundException(name);
         }
@@ -554,11 +426,6 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putFloat(String name, float value) {
-        return putFloatForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putFloat(String, float)} */
-    default boolean putFloatForUser(String name, float value, int userHandle) {
-        return putStringForUser(name, Float.toString(value), userHandle);
+        return putString(name, Float.toString(value));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index 561495e..7484368 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -27,7 +27,7 @@
 object SettingsProxyExt {
 
     /** Returns a flow of [Unit] that is invoked each time that content is updated. */
-    fun SettingsProxy.observerFlow(
+    fun UserSettingsProxy.observerFlow(
         @UserIdInt userId: Int,
         vararg names: String,
     ): Flow<Unit> {
@@ -44,4 +44,22 @@
             awaitClose { unregisterContentObserver(observer) }
         }
     }
+
+    /** Returns a flow of [Unit] that is invoked each time that content is updated. */
+    fun SettingsProxy.observerFlow(
+        vararg names: String,
+    ): Flow<Unit> {
+        return conflatedCallbackFlow {
+            val observer =
+                object : ContentObserver(null) {
+                    override fun onChange(selfChange: Boolean) {
+                        trySend(Unit)
+                    }
+                }
+
+            names.forEach { name -> registerContentObserver(name, observer) }
+
+            awaitClose { unregisterContentObserver(observer) }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
index d57d749..c67c603 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
@@ -21,5 +21,5 @@
  *
  * See {@link SettingsProxy} for details.
  */
-public interface SystemSettings extends SettingsProxy {
+public interface SystemSettings extends UserSettingsProxy {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index fba7ddf..658b299 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -20,6 +20,8 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
@@ -74,7 +76,7 @@
     }
 
     @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
         throw new UnsupportedOperationException(
                 "This method only exists publicly for Settings.Secure and Settings.Global");
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
new file mode 100644
index 0000000..0d6c0f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.settings.UserTracker;
+
+/**
+ * Used to interact with per-user Settings.Secure and Settings.System settings (but not
+ * Settings.Global, since those do not vary per-user)
+ * <p>
+ * This interface can be implemented to give instance method (instead of static method) versions
+ * of Settings.Secure and Settings.System. It can be injected into class constructors and then
+ * faked or mocked as needed in tests.
+ * <p>
+ * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed.
+ * <p>
+ * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
+ * normally found on {@link ContentResolver} instances, unifying setting related actions in one
+ * place.
+ */
+public interface UserSettingsProxy extends SettingsProxy {
+
+    /**
+     * Returns that {@link UserTracker} this instance was constructed with.
+     */
+    UserTracker getUserTracker();
+
+    /**
+     * Returns the user id for the associated {@link ContentResolver}.
+     */
+    default int getUserId() {
+        return getContentResolver().getUserId();
+    }
+
+    /**
+     * Returns the actual current user handle when querying with the current user. Otherwise,
+     * returns the passed in user id.
+     */
+    default int getRealUserHandle(int userHandle) {
+        if (userHandle != UserHandle.USER_CURRENT) {
+            return userHandle;
+        }
+        return getUserTracker().getUserId();
+    }
+
+    @Override
+    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    @Override
+    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+            ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                uri, false, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        getContentResolver().registerContentObserver(
+                uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+    }
+
+    /**
+     * Look up a name in the database.
+     * @param name to look up in the table
+     * @return the corresponding value, or null if not present
+     */
+    @Override
+    default String getString(String name) {
+        return getStringForUser(name, getUserId());
+    }
+
+    /**See {@link #getString(String)}. */
+    String getStringForUser(String name, int userHandle);
+
+    /**
+     * Store a name/value pair into the database. Values written by this method will be
+     * overridden if a restore happens in the future.
+     *
+     * @param name to store
+     * @param value to associate with the name
+     * @return true if the value was set, false on database errors
+     */
+    boolean putString(String name, String value, boolean overrideableByRestore);
+
+    @Override
+    default boolean putString(String name, String value) {
+        return putStringForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putString(String, String)}. */
+    boolean putStringForUser(String name, String value, int userHandle);
+
+    /** See {@link #putString(String, String)}. */
+    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+    @Override
+    default int getInt(String name, int def) {
+        return getIntForUser(name, def, getUserId());
+    }
+
+    /** See {@link #getInt(String, int)}. */
+    default int getIntForUser(String name, int def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return v != null ? Integer.parseInt(v) : def;
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    @Override
+    default int getInt(String name) throws Settings.SettingNotFoundException {
+        return getIntForUser(name, getUserId());
+    }
+
+    /** See {@link #getInt(String)}. */
+    default int getIntForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    @Override
+    default boolean putInt(String name, int value) {
+        return putIntForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putInt(String, int)}. */
+    default boolean putIntForUser(String name, int value, int userHandle) {
+        return putStringForUser(name, Integer.toString(value), userHandle);
+    }
+
+    @Override
+    default boolean getBool(String name, boolean def) {
+        return getBoolForUser(name, def, getUserId());
+    }
+
+    /** See {@link #getBool(String, boolean)}. */
+    default boolean getBoolForUser(String name, boolean def, int userHandle) {
+        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+    }
+
+    @Override
+    default boolean getBool(String name) throws Settings.SettingNotFoundException {
+        return getBoolForUser(name, getUserId());
+    }
+
+    /** See {@link #getBool(String)}. */
+    default boolean getBoolForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        return getIntForUser(name, userHandle) != 0;
+    }
+
+    @Override
+    default boolean putBool(String name, boolean value) {
+        return putBoolForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putBool(String, boolean)}. */
+    default boolean putBoolForUser(String name, boolean value, int userHandle) {
+        return putIntForUser(name, value ? 1 : 0, userHandle);
+    }
+
+    /** See {@link #getLong(String, long)}. */
+    default long getLongForUser(String name, long def, int userHandle) {
+        String valString = getStringForUser(name, userHandle);
+        return SettingsProxy.parseLongOrUseDefault(valString, def);
+    }
+
+    /** See {@link #getLong(String)}. */
+    default long getLongForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String valString = getStringForUser(name, userHandle);
+        return SettingsProxy.parseLongOrThrow(name, valString);
+    }
+
+    /** See {@link #putLong(String, long)}. */
+    default boolean putLongForUser(String name, long value, int userHandle) {
+        return putStringForUser(name, Long.toString(value), userHandle);
+    }
+
+    /** See {@link #getFloat(String)}. */
+    default float getFloatForUser(String name, float def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        return SettingsProxy.parseFloat(v, def);
+    }
+
+    /** See {@link #getFloat(String, float)}. */
+    default float getFloatForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        return SettingsProxy.parseFloatOrThrow(name, v);
+    }
+
+    /** See {@link #putFloat(String, float)} */
+    default boolean putFloatForUser(String name, float value, int userHandle) {
+        return putStringForUser(name, Float.toString(value), userHandle);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
index 51d2afa..1112d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
@@ -17,26 +17,58 @@
 
 package com.android.systemui.util.ui
 
+import com.android.systemui.util.ui.AnimatedValue.Animating
+import com.android.systemui.util.ui.AnimatedValue.NotAnimating
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.transformLatest
 
 /**
  * A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
  * [value] [isAnimating] in the UI.
  */
-data class AnimatedValue<T>(
-    val value: T,
-    val isAnimating: Boolean,
-)
+sealed interface AnimatedValue<out T> {
+
+    /** A [state][value] that is not actively animating in the UI. */
+    data class NotAnimating<out T>(val value: T) : AnimatedValue<T>
+
+    /**
+     * A [state][value] that is actively animating in the UI. Invoking [onStopAnimating] will signal
+     * the source of the state to stop animating.
+     */
+    data class Animating<out T>(
+        val value: T,
+        val onStopAnimating: () -> Unit,
+    ) : AnimatedValue<T>
+}
+
+/** The state held in this [AnimatedValue]. */
+inline val <T> AnimatedValue<T>.value: T
+    get() =
+        when (this) {
+            is Animating -> value
+            is NotAnimating -> value
+        }
+
+/** Returns whether or not this [AnimatedValue] is animating or not. */
+inline val <T> AnimatedValue<T>.isAnimating: Boolean
+    get() = this is Animating<T>
+
+/**
+ * If this [AnimatedValue] [isAnimating], then signal that the animation should be stopped.
+ * Otherwise, do nothing.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun AnimatedValue<*>.stopAnimating() {
+    if (this is Animating) onStopAnimating()
+}
 
 /**
  * An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
  * whether or not this event should start an animation.
  */
-data class AnimatableEvent<T>(
+data class AnimatableEvent<out T>(
     val value: T,
     val startAnimating: Boolean,
 )
@@ -47,16 +79,87 @@
  * [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
  * [AnimatedValue.isAnimating] will flip to `false`.
  */
-fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(
-    completionEvents: Flow<Any?>,
-): Flow<AnimatedValue<T>> = transformLatest { (value, startAnimating) ->
-    emit(AnimatedValue(value, isAnimating = startAnimating))
-    if (startAnimating) {
-        // Wait for a completion now that we've started animating
-        completionEvents
-            .map { Unit } // replace the event so that it's never `null`
-            .firstOrNull() // `null` indicates an empty flow
-            // emit the new state if the flow was not empty.
-            ?.run { emit(AnimatedValue(value, isAnimating = false)) }
+fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(): Flow<AnimatedValue<T>> =
+    transformLatest { (value, startAnimating) ->
+        if (startAnimating) {
+            val onCompleted = CompletableDeferred<Unit>()
+            emit(Animating(value) { onCompleted.complete(Unit) })
+            // Wait for a completion now that we've started animating
+            onCompleted.await()
+        }
+        emit(NotAnimating(value))
+    }
+
+/**
+ * Zip two [AnimatedValue]s together into a single [AnimatedValue], using [block] to combine the
+ * [value]s of each.
+ *
+ * If either [AnimatedValue] [isAnimating], then the result is also animating. Invoking
+ * [stopAnimating] on the result is equivalent to invoking [stopAnimating] on each input.
+ */
+inline fun <A, B, Z> zip(
+    valueA: AnimatedValue<A>,
+    valueB: AnimatedValue<B>,
+    block: (A, B) -> Z,
+): AnimatedValue<Z> {
+    val zippedValue = block(valueA.value, valueB.value)
+    return when (valueA) {
+        is Animating ->
+            when (valueB) {
+                is Animating ->
+                    Animating(zippedValue) {
+                        valueA.onStopAnimating()
+                        valueB.onStopAnimating()
+                    }
+                is NotAnimating -> Animating(zippedValue, valueA.onStopAnimating)
+            }
+        is NotAnimating ->
+            when (valueB) {
+                is Animating -> Animating(zippedValue, valueB.onStopAnimating)
+                is NotAnimating -> NotAnimating(zippedValue)
+            }
     }
 }
+
+/**
+ * Flattens a nested [AnimatedValue], the result of which holds the [value] of the inner
+ * [AnimatedValue].
+ *
+ * If either the outer or inner [AnimatedValue] [isAnimating], then the flattened result is also
+ * animating. Invoking [stopAnimating] on the result is equivalent to invoking [stopAnimating] on
+ * both the outer and inner values.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> AnimatedValue<AnimatedValue<T>>.flatten(): AnimatedValue<T> = flatMap { it }
+
+/**
+ * Returns an [AnimatedValue], the [value] of which is the result of the given [value] applied to
+ * [block].
+ */
+inline fun <A, B> AnimatedValue<A>.map(block: (A) -> B): AnimatedValue<B> =
+    when (this) {
+        is Animating -> Animating(block(value), ::stopAnimating)
+        is NotAnimating -> NotAnimating(block(value))
+    }
+
+/**
+ * Returns an [AnimatedValue] from the result of [block] being invoked on the [value] original
+ * [AnimatedValue].
+ *
+ * If either the input [AnimatedValue] or the result of [block] [isAnimating], then the flattened
+ * result is also animating. Invoking [stopAnimating] on the result is equivalent to invoking
+ * [stopAnimating] on both values.
+ */
+inline fun <A, B> AnimatedValue<A>.flatMap(block: (A) -> AnimatedValue<B>): AnimatedValue<B> =
+    when (this) {
+        is NotAnimating -> block(value)
+        is Animating ->
+            when (val inner = block(value)) {
+                is Animating ->
+                    Animating(inner.value) {
+                        onStopAnimating()
+                        inner.onStopAnimating()
+                    }
+                is NotAnimating -> Animating(inner.value, onStopAnimating)
+            }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
new file mode 100644
index 0000000..d3653b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.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.util.view
+
+import android.view.View
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more
+ * updates. New emissions lead to the previous binding call being cancelled if not completed.
+ * Dispose of the [DisposableHandle] returned by [bind] when done.
+ */
+suspend fun <T : View> Flow<T>.bindLatest(bind: (T) -> DisposableHandle?) {
+    this.collectLatest { view ->
+        val disposableHandle = bind(view)
+        disposableHandle?.awaitCancellationThenDispose()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 2d1e622..50d1547 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -234,6 +234,10 @@
         VOLUME_DIALOG_SLIDER(150),
         @UiEvent(doc = "The audio stream was set to silent via slider")
         VOLUME_DIALOG_SLIDER_TO_ZERO(151),
+        @UiEvent(doc = "ODI captions was clicked")
+        VOLUME_DIALOG_ODI_CAPTIONS_CLICKED(1503),
+        @UiEvent(doc = "ODI captions tooltip dismiss was clicked")
+        VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED(1504),
         @UiEvent(doc = "The audio volume was adjusted to silent via key")
         VOLUME_KEY_TO_ZERO(152),
         @UiEvent(doc = "The audio volume was adjusted to non-silent via key")
@@ -362,6 +366,10 @@
             if (tag == EVENT_SETTINGS_CLICK) {
                 sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
                 sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK);
+            } else if (tag == EVENT_ODI_CAPTIONS_CLICK) {
+                sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED);
+            } else if (tag == EVENT_ODI_CAPTIONS_TOOLTIP_CLICK) {
+                sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED);
             }
             return sb.toString();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 727d649..0ff308e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -129,12 +128,16 @@
 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.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.AlphaTintDrawableWrapper;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
+import com.android.systemui.util.settings.SecureSettings;
+
+import dagger.Lazy;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -284,7 +287,7 @@
     private boolean mIsAnimatingDismiss = false;
     private boolean mHasSeenODICaptionsTooltip;
     private ViewStub mODICaptionsTooltipViewStub;
-    private View mODICaptionsTooltipView = null;
+    @VisibleForTesting View mODICaptionsTooltipView = null;
 
     private final boolean mUseBackgroundBlur;
     private Consumer<Boolean> mCrossWindowBlurEnabledListener;
@@ -304,6 +307,8 @@
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
     private final FeatureFlags mFeatureFlags;
+    private final Lazy<SecureSettings> mSecureSettings;
+    private int mDialogTimeoutMillis;
 
     public VolumeDialogImpl(
             Context context,
@@ -320,7 +325,8 @@
             DevicePostureController devicePostureController,
             Looper looper,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Lazy<SecureSettings> secureSettings) {
         mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -351,6 +357,8 @@
         mUseBackgroundBlur =
             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
         mInteractionJankMonitor = interactionJankMonitor;
+        mSecureSettings = secureSettings;
+        mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
 
         dumpManager.registerDumpable("VolumeDialogImpl", this);
 
@@ -515,6 +523,8 @@
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setAlpha(0);
+        mDialogTimeoutMillis = mSecureSettings.get().getInt(
+                Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, DIALOG_TIMEOUT_MILLIS);
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setOnShowListener(dialog -> {
             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -527,7 +537,7 @@
                     .alpha(1)
                     .translationX(0)
                     .setDuration(mDialogShowAnimationDurationMs)
-                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, DIALOG_TIMEOUT_MILLIS))
+                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis))
                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
                     .withEndAction(() -> {
                         if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
@@ -1514,7 +1524,7 @@
                     AccessibilityManager.FLAG_CONTENT_TEXT
                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
         }
-        return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+        return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
 
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 cc9f3e1..e3b3c21 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -31,6 +31,7 @@
 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.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
@@ -38,6 +39,7 @@
 import com.android.systemui.volume.VolumePanelFactory;
 
 import dagger.Binds;
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -63,7 +65,8 @@
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Lazy<SecureSettings> secureSettings) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -79,7 +82,8 @@
                 devicePostureController,
                 Looper.getMainLooper(),
                 dumpManager,
-                featureFlags);
+                featureFlags,
+                secureSettings);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9dca013..fdf59664 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
         private WallpaperManager mWallpaperManager;
         private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
         private SurfaceHolder mSurfaceHolder;
+        private boolean mDrawn = false;
         @VisibleForTesting
         static final int MIN_SURFACE_WIDTH = 128;
         @VisibleForTesting
@@ -125,8 +126,6 @@
         private int mBitmapUsages = 0;
         private final Object mLock = new Object();
 
-        private boolean mIsLockscreenLiveWallpaperEnabled;
-
         CanvasEngine() {
             super();
             setFixedSizeAllowed(true);
@@ -170,12 +169,8 @@
                 Log.d(TAG, "onCreate");
             }
             mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
-            mIsLockscreenLiveWallpaperEnabled = mWallpaperManager
-                    .isLockscreenLiveWallpaperEnabled();
             mSurfaceHolder = surfaceHolder;
-            Rect dimensions = mIsLockscreenLiveWallpaperEnabled
-                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
-                    : mWallpaperManager.peekBitmapDimensions();
+            Rect dimensions = mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true);
             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
             mSurfaceHolder.setFixedSize(width, height);
@@ -239,6 +234,7 @@
 
         private void drawFrameSynchronized() {
             synchronized (mLock) {
+                if (mDrawn) return;
                 drawFrameInternal();
             }
         }
@@ -276,6 +272,7 @@
                 Rect dest = mSurfaceHolder.getSurfaceFrame();
                 try {
                     canvas.drawBitmap(bitmap, null, dest, null);
+                    mDrawn = true;
                 } finally {
                     surface.unlockCanvasAndPost(canvas);
                 }
@@ -324,10 +321,8 @@
             boolean loadSuccess = false;
             Bitmap bitmap;
             try {
-                bitmap = mIsLockscreenLiveWallpaperEnabled
-                        ? mWallpaperManager.getBitmapAsUser(
-                                mUserTracker.getUserId(), false, getSourceFlag(), true)
-                        : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
+                bitmap = mWallpaperManager.getBitmapAsUser(
+                        mUserTracker.getUserId(), false, getSourceFlag(), true);
                 if (bitmap != null
                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
                     throw new RuntimeException("Wallpaper is too large to draw!");
@@ -338,18 +333,11 @@
                 // be loaded, we will go into a cycle. Don't do a build where the
                 // default wallpaper can't be loaded.
                 Log.w(TAG, "Unable to load wallpaper!", exception);
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
-                } else {
-                    mWallpaperManager.clearWallpaper(
-                            WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId());
-                }
+                mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
 
                 try {
-                    bitmap = mIsLockscreenLiveWallpaperEnabled
-                            ? mWallpaperManager.getBitmapAsUser(
-                                    mUserTracker.getUserId(), false, getSourceFlag(), true)
-                            : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
+                    bitmap = mWallpaperManager.getBitmapAsUser(
+                            mUserTracker.getUserId(), false, getSourceFlag(), true);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
                     bitmap = null;
@@ -370,9 +358,7 @@
                     mBitmap.recycle();
                 }
                 mBitmap = bitmap;
-                mWideColorGamut = mIsLockscreenLiveWallpaperEnabled
-                        ? mWallpaperManager.wallpaperSupportsWcg(getSourceFlag())
-                        : mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
+                mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(getSourceFlag());
 
                 // +2 usages for the color extraction and the delayed unload.
                 mBitmapUsages += 2;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 897c4da..1e801ae 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -33,6 +33,7 @@
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.Display;
 import android.view.KeyEvent;
 
@@ -66,6 +67,7 @@
 import com.android.wm.shell.sysui.ShellInterface;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -371,6 +373,13 @@
 
     @Override
     public void dump(PrintWriter pw, String[] args) {
+        Log.d(TAG, "Dumping with args: " + String.join(", ", args));
+
+        // Strip out the SysUI "dependency" arg before sending to WMShell
+        if (args[0].equals("dependency")) {
+            args = Arrays.copyOfRange(args, 1, args.length);
+        }
+
         // Handle commands if provided
         if (mShell.handleCommand(args, pw)) {
             return;
diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index ea74510..c4b43e1 100644
--- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
+++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
@@ -16,10 +16,17 @@
 package com.android
 
 import android.content.Context
+import android.content.res.Resources
+import android.testing.TestableContext
+import android.testing.TestableResources
 import com.android.systemui.FakeSystemUiModule
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
 
@@ -31,12 +38,29 @@
             FakeSystemUiModule::class,
         ]
 )
-class SysUITestModule {
-    @Provides fun provideContext(test: SysuiTestCase): Context = test.context
+interface SysUITestModule {
 
-    @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context
+    @Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext
+    @Binds fun bindContext(testableContext: TestableContext): Context
+    @Binds @Application fun bindAppContext(context: Context): Context
+    @Binds @Application fun bindAppResources(resources: Resources): Resources
+    @Binds @Main fun bindMainResources(resources: Resources): Resources
+    @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
 
-    @Provides
-    fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher =
-        test.fakeBroadcastDispatcher
+    companion object {
+        @Provides
+        fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
+
+        @Provides
+        fun provideTestableResources(context: TestableContext): TestableResources =
+            context.getOrCreateTestableResources()
+
+        @Provides
+        fun provideResources(testableResources: TestableResources): Resources =
+            testableResources.resources
+
+        @Provides
+        fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher =
+            test.fakeBroadcastDispatcher
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index 167e341..0cb913b 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -19,10 +19,13 @@
 import android.app.admin.DevicePolicyManager
 import android.os.UserManager
 import android.util.DisplayMetrics
+import android.view.LayoutInflater
 import com.android.internal.logging.MetricsLogger
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardViewController
 import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.ScreenLifecycle
@@ -31,15 +34,18 @@
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import com.android.systemui.log.dagger.SceneFrameworkLog
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.model.SysUiState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+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.NotificationShadeDepthController
 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.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -71,7 +77,11 @@
     @get:Provides val keyguardBypassController: KeyguardBypassController = mock(),
     @get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
     @get:Provides val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(),
+    @get:Provides val layoutInflater: LayoutInflater = mock(),
+    @get:Provides
+    val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock(),
     @get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
+    @get:Provides val notifCollection: NotifCollection = mock(),
     @get:Provides val notificationListener: NotificationListener = mock(),
     @get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
     @get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
@@ -86,6 +96,9 @@
     @get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(),
     @get:Provides val statusBarWindowController: StatusBarWindowController = mock(),
     @get:Provides val wakefulnessLifecycle: WakefulnessLifecycle = mock(),
+    @get:Provides val keyguardViewController: KeyguardViewController = mock(),
+    @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(),
+    @get:Provides val sysuiState: SysUiState = mock(),
 
     // log buffers
     @get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 81fef7a..e429446 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -35,9 +35,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
+import java.io.PrintWriter
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -45,26 +48,21 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
 
 @SmallTest
 class ActiveUnlockConfigTest : SysuiTestCase() {
     private lateinit var secureSettings: FakeSettings
-    @Mock
-    private lateinit var contentResolver: ContentResolver
-    @Mock
-    private lateinit var handler: Handler
-    @Mock
-    private lateinit var dumpManager: DumpManager
-    @Mock
-    private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var contentResolver: ContentResolver
+    @Mock private lateinit var handler: Handler
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mockPrintWriter: PrintWriter
 
-    @Captor
-    private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+    @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
 
     private lateinit var activeUnlockConfig: ActiveUnlockConfig
     private var currentUser: Int = 0
@@ -73,14 +71,16 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        currentUser = KeyguardUpdateMonitor.getCurrentUser()
+        whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser)
         secureSettings = FakeSettings()
-        activeUnlockConfig = ActiveUnlockConfig(
+        activeUnlockConfig =
+            ActiveUnlockConfig(
                 handler,
                 secureSettings,
                 contentResolver,
+                selectedUserInteractor,
                 dumpManager
-        )
+            )
     }
 
     @Test
@@ -92,8 +92,9 @@
     fun onWakeupSettingChanged() {
         // GIVEN no active unlock settings enabled
         assertFalse(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
         )
 
         // WHEN unlock on wake is allowed
@@ -102,16 +103,19 @@
 
         // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
         assertTrue(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
         )
         assertTrue(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
         )
         assertTrue(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
         )
     }
 
@@ -119,8 +123,9 @@
     fun onUnlockIntentSettingChanged() {
         // GIVEN no active unlock settings enabled
         assertFalse(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
         )
 
         // WHEN unlock on biometric failed is allowed
@@ -128,12 +133,21 @@
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
 
         // THEN active unlock triggers allowed on: biometric failure ONLY
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
+        )
     }
 
     @Test
@@ -141,24 +155,39 @@
         // GIVEN no active unlock settings enabled and triggering unlock intent on biometric
         // enrollment setting is disabled (empty string is disabled, null would use the default)
         secureSettings.putStringForUser(
-                ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+            "",
+            currentUser
+        )
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
+        )
 
         // WHEN unlock on biometric failed is allowed
         secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
 
         // THEN active unlock triggers allowed on: biometric failure ONLY
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
+        )
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
+        )
     }
 
     @Test
@@ -168,16 +197,21 @@
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
 
         // WHEN face error timeout (3), allow trigger active unlock
-        secureSettings.putStringForUser(
-            ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
+        secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
 
         // THEN active unlock triggers allowed on error TIMEOUT
-        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
-                BiometricFaceConstants.FACE_ERROR_TIMEOUT))
+        assertTrue(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+                BiometricFaceConstants.FACE_ERROR_TIMEOUT
+            )
+        )
 
-        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
-                BiometricFaceConstants.FACE_ERROR_CANCELED))
+        assertFalse(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+                BiometricFaceConstants.FACE_ERROR_CANCELED
+            )
+        )
     }
 
     @Test
@@ -189,21 +223,34 @@
         // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
         secureSettings.putStringForUser(
             ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
-                "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
-                        "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
-            currentUser)
+            "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
+                "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
+            currentUser
+        )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
 
         // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
-        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED))
-        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED))
+        assertTrue(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
+            )
+        )
 
-        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_GOOD))
-        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED))
+        assertFalse(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_GOOD
+            )
+        )
+        assertFalse(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED
+            )
+        )
     }
 
     @Test
@@ -215,20 +262,25 @@
         // GIVEN fingerprint and face are NOT enrolled
         activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
         `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
-        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false)
 
         // WHEN unlock intent is allowed when NO biometrics are enrolled (0)
 
         secureSettings.putStringForUser(
             ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-            "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
+            "${ActiveUnlockConfig.BiometricType.NONE.intValue}",
+            currentUser
+        )
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
 
         // THEN active unlock triggers allowed on unlock intent
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
     }
 
     @Test
@@ -240,38 +292,48 @@
         // GIVEN fingerprint and face are both enrolled
         activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
         `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
-        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true)
 
         // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
         // are enrolled
         secureSettings.putStringForUser(
-                ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-                "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
-                        "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
-            currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
+            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+            "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
+                "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
+            currentUser
+        )
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
 
         // THEN active unlock triggers NOT allowed on unlock intent
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
 
         // WHEN fingerprint ONLY enrolled
         `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
-        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true)
 
         // THEN active unlock triggers allowed on unlock intent
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
 
         // WHEN face ONLY enrolled
         `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
-        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false)
 
         // THEN active unlock triggers allowed on unlock intent
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
     }
 
     @Test
@@ -280,7 +342,8 @@
         secureSettings.putIntForUser(
             ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
             PowerManager.WAKE_REASON_LIFT,
-            currentUser)
+            currentUser
+        )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
 
         // THEN only WAKE_REASON_LIFT is considered an unlock intent
@@ -299,16 +362,18 @@
         secureSettings.putStringForUser(
             ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
             PowerManager.WAKE_REASON_LIFT.toString() +
-                    "|" +
-                    PowerManager.WAKE_REASON_TAP.toString(),
+                "|" +
+                PowerManager.WAKE_REASON_TAP.toString(),
             currentUser
         )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
 
         // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
-            if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
-                wakeReason == PowerManager.WAKE_REASON_TAP) {
+            if (
+                wakeReason == PowerManager.WAKE_REASON_LIFT ||
+                    wakeReason == PowerManager.WAKE_REASON_TAP
+            ) {
                 assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
             } else {
                 assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
@@ -316,26 +381,36 @@
         }
         assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT))
         assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
-        assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
-            PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+        assertFalse(
+            activeUnlockConfig.isWakeupConsideredUnlockIntent(
+                PowerManager.WAKE_REASON_UNFOLD_DEVICE
+            )
+        )
     }
 
     @Test
     fun isWakeupConsideredUnlockIntent_emptyValues() {
         // GIVEN lift and tap are considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ",
-            currentUser)
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+            " ",
+            currentUser
+        )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
 
         // THEN no wake up gestures are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
             assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
         }
-        assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
-            PowerManager.WAKE_REASON_LIFT))
+        assertFalse(
+            activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT)
+        )
         assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
-        assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
-            PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+        assertFalse(
+            activeUnlockConfig.isWakeupConsideredUnlockIntent(
+                PowerManager.WAKE_REASON_UNFOLD_DEVICE
+            )
+        )
     }
 
     @Test
@@ -343,11 +418,12 @@
         verifyRegisterSettingObserver()
 
         // GIVEN lift is considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
-            PowerManager.WAKE_REASON_LIFT.toString(), currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
-        ))
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+            PowerManager.WAKE_REASON_LIFT.toString(),
+            currentUser
+        )
+        updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
 
         // THEN only WAKE_REASON_LIFT is considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -364,11 +440,12 @@
         verifyRegisterSettingObserver()
 
         // GIVEN lift and tap are considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
-            " ", currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
-        ))
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+            " ",
+            currentUser
+        )
+        updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
 
         // THEN no wake up gestures are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -381,20 +458,21 @@
         verifyRegisterSettingObserver()
 
         // GIVEN lift and tap are considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
             PowerManager.WAKE_REASON_LIFT.toString() +
-                    "|" +
-                    PowerManager.WAKE_REASON_TAP.toString(),
+                "|" +
+                PowerManager.WAKE_REASON_TAP.toString(),
             currentUser
         )
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
-        ))
+        updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
 
         // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
-            if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
-                wakeReason == PowerManager.WAKE_REASON_TAP) {
+            if (
+                wakeReason == PowerManager.WAKE_REASON_LIFT ||
+                    wakeReason == PowerManager.WAKE_REASON_TAP
+            ) {
                 assertTrue(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason))
             } else {
                 assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason))
@@ -405,13 +483,16 @@
     @Test
     fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() {
         // GIVEN an invalid input (-1)
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-            "-1", currentUser)
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+            "-1",
+            currentUser
+        )
 
         // WHEN the setting updates
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
 
         // THEN no exception thrown
         activeUnlockConfig.dump(mockPrintWriter, emptyArray())
@@ -419,12 +500,7 @@
 
     private fun updateSetting(uri: Uri) {
         verifyRegisterSettingObserver()
-        settingsObserverCaptor.value.onChange(
-                false,
-                listOf(uri),
-                0,
-                0 /* flags */
-        )
+        settingsObserverCaptor.value.onChange(false, listOf(uri), 0, 0 /* flags */)
     }
 
     private fun verifyRegisterSettingObserver() {
@@ -433,19 +509,21 @@
         verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
         verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
         verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
-        verifyRegisterSettingObserver(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
-        verifyRegisterSettingObserver(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
-        ))
+        verifyRegisterSettingObserver(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
+        verifyRegisterSettingObserver(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)
+        )
     }
 
     private fun verifyRegisterSettingObserver(uri: Uri) {
-        verify(contentResolver).registerContentObserver(
+        verify(contentResolver)
+            .registerContentObserver(
                 eq(uri),
                 eq(false),
                 capture(settingsObserverCaptor),
-                eq(UserHandle.USER_ALL))
+                eq(UserHandle.USER_ALL)
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index d506584..cdd0eb0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -50,6 +50,7 @@
 
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,7 +65,7 @@
 @SmallTest
 public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
 
-    private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
+    private static final int TARGET_USER_ID = 0;
 
     private AdminSecondaryLockScreenController mTestController;
     private ComponentName mComponentName;
@@ -80,12 +81,15 @@
     private KeyguardSecurityCallback mKeyguardCallback;
     @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID);
 
         mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
         mKeyguardSecurityContainer.setId(View.generateViewId());
@@ -106,7 +110,8 @@
                 new Binder())).getSurfacePackage();
 
         mTestController = new AdminSecondaryLockScreenController.Factory(
-                mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
+                mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler,
+                mSelectedUserInteractor)
                 .create(mKeyguardCallback);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6afa525..4d8768f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,9 +25,11 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+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.log.LogBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockController
@@ -47,8 +49,8 @@
 import java.util.TimeZone
 import java.util.concurrent.Executor
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.yield
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -90,10 +92,10 @@
     @Mock private lateinit var smallClockEvents: ClockFaceEvents
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
     @Mock private lateinit var parentView: View
-    @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
     private lateinit var repository: FakeKeyguardRepository
     @Mock private lateinit var smallLogBuffer: LogBuffer
     @Mock private lateinit var largeLogBuffer: LogBuffer
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var underTest: ClockEventController
 
     @Before
@@ -125,17 +127,13 @@
 
         withDeps.featureFlags.apply {
             set(Flags.REGION_SAMPLING, false)
-            set(Flags.DOZING_MIGRATION_1, false)
+            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
             set(Flags.FACE_AUTH_REFACTOR, false)
         }
         underTest =
             ClockEventController(
                 withDeps.keyguardInteractor,
-                KeyguardTransitionInteractorFactory.create(
-                        scope = TestScope().backgroundScope,
-                        featureFlags = withDeps.featureFlags,
-                    )
-                    .keyguardTransitionInteractor,
+                keyguardTransitionInteractor,
                 broadcastDispatcher,
                 batteryController,
                 keyguardUpdateMonitor,
@@ -316,6 +314,68 @@
         }
 
     @Test
+    fun listenForDozeAmountTransition_updatesClockDozeAmount() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep)
+
+            val job = underTest.listenForDozeAmountTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0.4f
+                )
+            yield()
+
+            verify(animations, times(2)).doze(0.4f)
+
+            job.cancel()
+        }
+
+    @Test
+    fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+                .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToAodTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            yield()
+
+            verify(animations, times(2)).doze(1f)
+
+            job.cancel()
+        }
+
+    @Test
+    fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+                .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToAodTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            yield()
+
+            verify(animations, never()).doze(1f)
+
+            job.cancel()
+        }
+
+    @Test
     fun unregisterListeners_validate() =
         runBlocking(IMMEDIATE) {
             underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index 30fed0b..c61b11a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -59,6 +60,7 @@
     @Mock lateinit var metricsLogger: MetricsLogger
     @Mock lateinit var lockPatternUtils: LockPatternUtils
     @Mock lateinit var packageManager: PackageManager
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
     val fakeSystemClock = FakeSystemClock()
     val mainExecutor = FakeExecutor(fakeSystemClock)
     val backgroundExecutor = FakeExecutor(fakeSystemClock)
@@ -79,7 +81,8 @@
                 metricsLogger,
                 lockPatternUtils,
                 mainExecutor,
-                backgroundExecutor
+                backgroundExecutor,
+                mSelectedUserInteractor,
             )
         context.setMockPackageManager(packageManager)
         Mockito.`when`(emergencyButton.context).thenReturn(context)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 42f65f6..7f20d9a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -37,12 +37,13 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +82,8 @@
     private EmergencyButtonController mEmergencyButtonController;
 
     private FakeFeatureFlags mFeatureFlags;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
 
     @Before
@@ -105,7 +108,7 @@
         return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
-                mEmergencyButtonController, mFeatureFlags) {
+                mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) {
             @Override
             void resetState() {
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index 91b544b..634dac1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -25,6 +25,7 @@
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,8 +35,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -50,6 +51,8 @@
     lateinit var sessionTracker: SessionTracker
     @Mock
     lateinit var sessionId: InstanceId
+    @Mock
+    lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Captor
     lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -65,7 +68,8 @@
         keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
                 uiEventLogger,
                 keyguardUpdateMonitor,
-                sessionTracker)
+                sessionTracker,
+                mSelectedUserInteractor)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 5273e0e5..ac40ba6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeast;
@@ -35,8 +36,8 @@
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.ConfigurationState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -50,12 +51,19 @@
 import com.android.systemui.plugins.ClockFaceEvents;
 import com.android.systemui.plugins.ClockTickRate;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -136,6 +144,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
+        setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
         mFakeDateView.setTag(R.id.tag_smartspace_view, new Object());
         mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object());
         mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object());
@@ -176,12 +185,18 @@
                 mKeyguardSliceViewController,
                 mNotificationIconAreaController,
                 mSmartspaceController,
+                mock(ConfigurationController.class),
+                mock(ScreenOffAnimationController.class),
                 mKeyguardUnlockAnimationController,
                 mSecureSettings,
                 mExecutor,
                 mDumpManager,
                 mClockEventController,
                 mLogBuffer,
+                mock(NotificationIconContainerAlwaysOnDisplayViewModel.class),
+                mock(ConfigurationState.class),
+                mock(DozeParameters.class),
+                mock(AlwaysOnDisplayNotificationIconViewStore.class),
                 KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
                 mFakeFeatureFlags
         );
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d8a2c5f..3fbcf6d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,12 +24,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.whenever
 import org.junit.Before
@@ -62,6 +63,7 @@
     @Mock lateinit var mainExecutor: DelayableExecutor
     @Mock lateinit var falsingCollector: FalsingCollector
     @Mock lateinit var keyguardViewController: KeyguardViewController
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
     @Mock
     private lateinit var mKeyguardMessageAreaController:
@@ -106,7 +108,8 @@
                 falsingCollector,
                 keyguardViewController,
                 postureController,
-                fakeFeatureFlags
+                fakeFeatureFlags,
+                mSelectedUserInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index dc1618d..74c9225 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -24,15 +24,16 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.flags.FakeFeatureFlags
 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.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -75,6 +76,9 @@
         KeyguardMessageAreaController.Factory
 
     @Mock
+    private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
+    @Mock
     private lateinit var mKeyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
@@ -108,7 +112,8 @@
                 mEmergencyButtonController,
                 mKeyguardMessageAreaControllerFactory,
                 mPostureController,
-                fakeFeatureFlags
+                fakeFeatureFlags,
+                mSelectedUserInteractor
             )
         mKeyguardPatternView.onAttachedToWindow()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4a24e4a..d41c249 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -34,10 +34,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.SingleTapClassifier;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,11 +76,11 @@
     private EmergencyButtonController mEmergencyButtonController;
     private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
-    private SingleTapClassifier mSingleTapClassifier;
-    @Mock
     private View mDeleteButton;
     @Mock
     private View mOkButton;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private NumPadKey[] mButtons = new NumPadKey[]{};
 
     private KeyguardPinBasedInputViewController mKeyguardPinViewController;
@@ -108,7 +108,8 @@
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
-                mEmergencyButtonController, mFalsingCollector, featureFlags) {
+                mEmergencyButtonController, mFalsingCollector, featureFlags,
+                mSelectedUserInteractor) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9df4dd4..80d45bc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -25,15 +25,16 @@
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.res.R
 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
@@ -84,6 +85,7 @@
     @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
@@ -133,7 +135,8 @@
             mEmergencyButtonController,
             falsingCollector,
             postureController,
-            featureFlags
+            featureFlags,
+            mSelectedUserInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 62f9a9d..fda4133 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -63,7 +63,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.user.domain.interactor.UserInteractor
+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.argThat
@@ -136,7 +136,7 @@
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
     @Mock private lateinit var audioManager: AudioManager
-    @Mock private lateinit var userInteractor: UserInteractor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
@@ -215,10 +215,10 @@
                 null,
                 keyguardViewController,
                 postureController,
-                featureFlags
+                featureFlags,
+                mSelectedUserInteractor,
             )
 
-        whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
         sceneTestUtils = SceneTestUtils(this)
         sceneInteractor = sceneTestUtils.sceneInteractor()
         keyguardTransitionInteractor =
@@ -260,7 +260,7 @@
                 mock(),
                 mock(),
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
-                userInteractor,
+                mSelectedUserInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
                 keyguardTransitionInteractor,
@@ -810,7 +810,6 @@
                     SceneKey.Bouncer,
                     flowOf(.5f),
                     false,
-                    isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
@@ -826,8 +825,7 @@
                     SceneKey.Bouncer,
                     SceneKey.Gone,
                     flowOf(.5f),
-                    false,
-                    isUserInputOngoing = flowOf(false),
+                    false
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
@@ -844,8 +842,7 @@
                     SceneKey.Gone,
                     SceneKey.Bouncer,
                     flowOf(.5f),
-                    false,
-                    isUserInputOngoing = flowOf(false),
+                    false
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
@@ -863,8 +860,7 @@
                     SceneKey.Bouncer,
                     SceneKey.Gone,
                     flowOf(.5f),
-                    false,
-                    isUserInputOngoing = flowOf(false),
+                    false
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
@@ -880,7 +876,6 @@
                     SceneKey.Lockscreen,
                     flowOf(.5f),
                     false,
-                    isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
@@ -898,7 +893,6 @@
                     SceneKey.Gone,
                     flowOf(.5f),
                     false,
-                    isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 4290b8b..94c3bde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -24,11 +24,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.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
@@ -58,6 +59,7 @@
     @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>
@@ -90,6 +92,7 @@
                 falsingCollector,
                 emergencyButtonController,
                 fakeFeatureFlags,
+                mSelectedUserInteractor
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 31ee641..7b1f302 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -24,11 +24,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.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
@@ -54,6 +55,7 @@
     @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>
@@ -89,6 +91,7 @@
                 falsingCollector,
                 emergencyButtonController,
                 fakeFeatureFlags,
+                mSelectedUserInteractor,
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 3b8e02f..22c75d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -31,6 +33,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -60,6 +63,7 @@
     @Mock protected FeatureFlags mFeatureFlags;
     @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
+    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock protected DumpManager mDumpManager;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected FakePowerRepository mFakePowerRepository;
@@ -90,6 +94,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 PowerInteractorFactory.create(
                         mFakePowerRepository
@@ -105,8 +110,8 @@
                 };
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+        when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
     }
 
     protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 47be236..efb0887 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,7 +38,6 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
-import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -82,7 +81,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -157,8 +155,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -240,8 +238,6 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -278,18 +274,17 @@
     @Mock
     private UsbPortStatus mUsbPortStatus;
     @Mock
-    private Uri mURI;
-    @Mock
     private TaskStackChangeListeners mTaskStackChangeListeners;
     @Mock
     private IActivityTaskManager mActivityTaskManager;
     @Mock
     private WakefulnessLifecycle mWakefulness;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private final int mCurrentUserId = 100;
-    private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
 
     @Captor
     private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback>
@@ -337,8 +332,8 @@
                 .startMocking();
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
-        KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
-        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId);
+        when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId);
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.systemui.res.R.integer.config_face_auth_supported_posture,
@@ -351,13 +346,11 @@
 
         mContext.getOrCreateTestableResources().addOverride(com.android.systemui.res
                         .R.array.config_fingerprint_listen_on_occluding_activity_packages,
-                new String[]{ PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY });
+                new String[]{PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY});
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
 
-        when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
-
         final ContentResolver contentResolver = mContext.getContentResolver();
         ExtendedMockito.spyOn(contentResolver);
         doNothing().when(contentResolver)
@@ -421,6 +414,7 @@
     }
 
     private void setupFingerprintAuth(boolean isClass3) throws RemoteException {
+        when(mAuthController.isFingerprintEnrolled(anyInt())).thenReturn(true);
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         mFingerprintSensorProperties = List.of(
@@ -1005,11 +999,14 @@
     @Test
     public void trustAgentHasTrust() {
         // WHEN user has trust
-        mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
 
         // THEN user is considered as "having trust" and bouncer can be skipped
-        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
-        Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
+        Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+                mSelectedUserInteractor.getSelectedUserId()));
     }
 
     @Test
@@ -1027,15 +1024,19 @@
     @Test
     public void trustAgentHasTrust_fingerprintLockout() {
         // GIVEN user has trust
-        mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
-        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
+        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
 
         // WHEN fingerprint is lock out
         fingerprintErrorTemporaryLockOut();
 
         // THEN user is NOT considered as "having trust" and bouncer cannot be skipped
-        Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
-        Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+        Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
+        Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+                mSelectedUserInteractor.getSelectedUserId()));
     }
 
     @Test
@@ -1217,7 +1218,7 @@
         mTestableLooper.processAllMessages();
         lockscreenBypassIsAllowed();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
                 new ArrayList<>());
         keyguardIsVisible();
         verifyFaceAuthenticateCall();
@@ -1249,7 +1250,7 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
         keyguardIsVisible();
         verifyFaceAuthenticateNeverCalled();
     }
@@ -1287,7 +1288,7 @@
     public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
         // test whether face will be skipped if authenticated, so the value of isClass3Biometric
         // doesn't matter here
-        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+        mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
                 true /* isClass3Biometric */);
         setKeyguardBouncerVisibility(true);
         mTestableLooper.processAllMessages();
@@ -1333,7 +1334,7 @@
 
     @Test
     public void testGetUserCanSkipBouncer_whenFace() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
@@ -1342,14 +1343,14 @@
     public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
 
     @Test
     public void testGetUserCanSkipBouncer_whenFingerprint() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
@@ -1358,7 +1359,7 @@
     public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
@@ -1372,7 +1373,8 @@
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1);
         assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1);
 
-        mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {});
+        mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {
+        });
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0);
         assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0);
     }
@@ -1446,7 +1448,7 @@
 
     @Test
     public void testGetUserCanSkipBouncer_whenTrust() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
                 user, 0 /* flags */, new ArrayList<>());
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
@@ -1493,7 +1495,8 @@
     @Test
     public void testIsUserUnlocked() {
         // mUserManager will report the user as unlocked on @Before
-        assertThat(mKeyguardUpdateMonitor.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser()))
+        assertThat(
+                mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId()))
                 .isTrue();
         // Invalid user should not be unlocked.
         int randomUser = 99;
@@ -1502,7 +1505,7 @@
 
     @Test
     public void testTrustUsuallyManaged_whenTrustChanges() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true);
         mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user);
         assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue();
@@ -1510,7 +1513,7 @@
 
     @Test
     public void testTrustUsuallyManaged_resetWhenUserIsRemoved() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true);
         mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user);
         assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue();
@@ -1521,9 +1524,9 @@
 
     @Test
     public void testSecondaryLockscreenRequirement() {
-        KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(UserHandle.myUserId());
         when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         String packageName = "fake.test.package";
         String cls = "FakeService";
         ServiceInfo serviceInfo = new ServiceInfo();
@@ -1680,7 +1683,7 @@
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
 
         // WHEN user loses smart unlock trust
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
 
         // THEN we should still listen for udfps
@@ -1724,7 +1727,7 @@
 
         // WHEN trust is enabled (ie: via smartlock)
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1738,7 +1741,7 @@
 
         // WHEN face authenticated
         mKeyguardUpdateMonitor.onFaceAuthenticated(
-                KeyguardUpdateMonitor.getCurrentUser(), false);
+                mSelectedUserInteractor.getSelectedUserId(), false);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1825,7 +1828,7 @@
     public void testShowTrustGrantedMessage_onTrustGranted() {
         // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
                 Arrays.asList("Unlocked by wearable"));
 
         // THEN the showTrustGrantedMessage should be called with the first message
@@ -2256,7 +2259,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
                 null /* trustGrantedMessages */);
 
@@ -2281,7 +2284,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
                 null /* trustGrantedMessages */);
 
@@ -2333,7 +2336,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
                         | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
                 null /* trustGrantedMessages */);
@@ -2362,7 +2365,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId, not the current userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
                 null /* trustGrantedMessages */);
 
@@ -2388,7 +2391,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId, not the current userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
                         | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
                 null /* trustGrantedMessages */);
@@ -2423,7 +2426,8 @@
         // WHEN strong auth changes and device is in user lockdown
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         userDeviceLockDown();
-        mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser());
+        mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(
+                mSelectedUserInteractor.getSelectedUserId());
         mTestableLooper.processAllMessages();
 
         // THEN face and fingerprint listening are cancelled
@@ -2451,7 +2455,8 @@
 
         // WHEN non-strong biometric allowed changes
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-        mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser());
+        mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(
+                mSelectedUserInteractor.getSelectedUserId());
         mTestableLooper.processAllMessages();
 
         // THEN face and fingerprint listening are cancelled
@@ -2516,13 +2521,15 @@
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // WHEN the assistant is visible
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
         // THEN request unlock with keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2531,7 +2538,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
         bouncerFullyVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2542,7 +2550,8 @@
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
 
         // ALWAYS request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2554,7 +2563,8 @@
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2566,7 +2576,8 @@
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
 
         // THEN request unlock with NO keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(false));
     }
 
@@ -2578,7 +2589,8 @@
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2590,7 +2602,8 @@
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2600,7 +2613,8 @@
         // GIVEN shouldTriggerActiveUnlock
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
         lockscreenBypassIsNotAllowed();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2612,7 +2626,8 @@
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2701,7 +2716,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2717,7 +2733,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2726,7 +2743,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2742,7 +2760,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock WITHOUT a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(false));
     }
 
@@ -2751,7 +2770,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2767,7 +2787,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2777,7 +2798,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2793,7 +2815,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock WITHOUT a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(false));
     }
 
@@ -2857,35 +2880,41 @@
         assertThat(captor.getValue().getWakeReason())
                 .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
     }
+
     @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
 
         assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+                mSelectedUserInteractor.getSelectedUserId())).isFalse();
 
         mFingerprintAuthenticatorsRegisteredCallback
                 .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
 
         verifyFingerprintAuthenticateCall();
         assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+                mSelectedUserInteractor.getSelectedUserId())).isTrue();
     }
+
     @Test
     public void testFaceSensorProperties() throws RemoteException {
+        // GIVEN no face sensor properties
+        when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
 
-        assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
-                KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+        // THEN face is not possible
+        assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible(
+                mSelectedUserInteractor.getSelectedUserId())).isFalse();
 
+        // WHEN there are face sensor properties
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-        biometricsEnabledForCurrentUser();
 
+        // THEN face is possible but face does NOT start listening immediately
+        assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible(
+                mSelectedUserInteractor.getSelectedUserId())).isTrue();
         verifyFaceAuthenticateNeverCalled();
         verifyFaceDetectNeverCalled();
-        assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
-                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
     }
 
     @Test
@@ -2917,13 +2946,13 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
                 null /* trustGrantedMessages */);
 
         // THEN onTrustChanged is called FIRST
         final InOrder inOrder = Mockito.inOrder(callback);
-        inOrder.verify(callback).onTrustChanged(eq(getCurrentUser()));
+        inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
 
         // AND THEN onTrustGrantedForCurrentUser callback called
         inOrder.verify(callback).onTrustGrantedForCurrentUser(
@@ -3143,9 +3172,6 @@
     }
 
     private void mockCanBypassLockscreen(boolean canBypass) {
-        // force update the isFaceEnrolled cache:
-        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
-
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
     }
@@ -3235,22 +3261,23 @@
 
     private void biometricsNotDisabledThroughDevicePolicyManager() {
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
-                KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+                mSelectedUserInteractor.getSelectedUserId())).thenReturn(0);
     }
 
     private void biometricsEnabledForCurrentUser() throws RemoteException {
-        mBiometricEnabledOnKeyguardCallback.onChanged(true, KeyguardUpdateMonitor.getCurrentUser());
+        mBiometricEnabledOnKeyguardCallback.onChanged(true,
+                mSelectedUserInteractor.getSelectedUserId());
     }
 
     private void biometricsDisabledForCurrentUser() throws RemoteException {
         mBiometricEnabledOnKeyguardCallback.onChanged(
                 false,
-                KeyguardUpdateMonitor.getCurrentUser()
+                mSelectedUserInteractor.getSelectedUserId()
         );
     }
 
     private void primaryAuthRequiredEncrypted() {
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
     }
@@ -3261,7 +3288,7 @@
     }
 
     private void primaryAuthNotRequiredByStrongAuthTracker() {
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(0);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
     }
@@ -3270,7 +3297,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 false,
                 false,
-                KeyguardUpdateMonitor.getCurrentUser(),
+                mSelectedUserInteractor.getSelectedUserId(),
                 -1,
                 new ArrayList<>()
         );
@@ -3435,7 +3462,7 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context, mUserTracker,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mSecureSettings, mDumpManager,
+                    mBroadcastDispatcher, mDumpManager,
                     mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
@@ -3447,7 +3474,7 @@
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider),
                     mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker,
-                    mWakefulness);
+                    mWakefulness, mSelectedUserInteractor);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
new file mode 100644
index 0000000..b1421b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -0,0 +1,162 @@
+package com.android.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.camera2.CameraManager
+import android.testing.AndroidTestingRunner
+import android.util.PathParser
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.concurrent.Executor
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+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
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CameraAvailabilityListenerTest : SysuiTestCase() {
+    companion object {
+        const val EXCLUDED_PKG = "test.excluded.package"
+        const val CAMERA_ID_FRONT = "0"
+        const val CAMERA_ID_INNER = "1"
+        const val PROTECTION_PATH_STRING_FRONT = "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
+        const val PROTECTION_PATH_STRING_INNER = "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
+        val PATH_RECT_FRONT = rectFromPath(pathFromString(PROTECTION_PATH_STRING_FRONT))
+        val PATH_RECT_INNER = rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER))
+
+        private fun pathFromString(pathString: String): Path {
+            val spec = pathString.trim()
+            val p: Path
+            try {
+                p = PathParser.createPathFromPathData(spec)
+            } catch (e: Throwable) {
+                throw IllegalArgumentException("Invalid protection path", e)
+            }
+
+            return p
+        }
+
+        private fun rectFromPath(path: Path): Rect {
+            val computed = RectF()
+            path.computeBounds(computed)
+            return Rect(
+                computed.left.roundToInt(),
+                computed.top.roundToInt(),
+                computed.right.roundToInt(),
+                computed.bottom.roundToInt()
+            )
+        }
+    }
+
+    @Mock private lateinit var cameraManager: CameraManager
+    @Mock
+    private lateinit var cameraTransitionCb: CameraAvailabilityListener.CameraTransitionCallback
+    private lateinit var cameraAvailabilityListener: CameraAvailabilityListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.string.config_protectedCameraId, CAMERA_ID_FRONT)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(
+                R.string.config_frontBuiltInDisplayCutoutProtection,
+                PROTECTION_PATH_STRING_FRONT
+            )
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.string.config_protectedInnerCameraId, CAMERA_ID_INNER)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(
+                R.string.config_innerBuiltInDisplayCutoutProtection,
+                PROTECTION_PATH_STRING_INNER
+            )
+
+        context.addMockSystemService(CameraManager::class.java, cameraManager)
+
+        cameraAvailabilityListener =
+            CameraAvailabilityListener.Factory.build(context, context.mainExecutor)
+    }
+
+    @Test
+    fun testFrontCamera() {
+        var path: Path? = null
+        var rect: Rect? = null
+        val callback =
+            object : CameraAvailabilityListener.CameraTransitionCallback {
+                override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+                    path = protectionPath
+                    rect = bounds
+                }
+
+                override fun onHideCameraProtection() {}
+            }
+
+        cameraAvailabilityListener.addTransitionCallback(callback)
+        cameraAvailabilityListener.startListening()
+
+        val callbackCaptor = withArgCaptor {
+            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        }
+
+        callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, "")
+        assertNotNull(path)
+        assertEquals(PATH_RECT_FRONT, rect)
+    }
+
+    @Test
+    fun testInnerCamera() {
+        var path: Path? = null
+        var rect: Rect? = null
+        val callback =
+            object : CameraAvailabilityListener.CameraTransitionCallback {
+                override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+                    path = protectionPath
+                    rect = bounds
+                }
+
+                override fun onHideCameraProtection() {}
+            }
+
+        cameraAvailabilityListener.addTransitionCallback(callback)
+        cameraAvailabilityListener.startListening()
+
+        val callbackCaptor = withArgCaptor {
+            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        }
+
+        callbackCaptor.onCameraOpened(CAMERA_ID_INNER, "")
+        assertNotNull(path)
+        assertEquals(PATH_RECT_INNER, rect)
+    }
+
+    @Test
+    fun testExcludedPackage() {
+        cameraAvailabilityListener.addTransitionCallback(cameraTransitionCb)
+        cameraAvailabilityListener.startListening()
+
+        val callbackCaptor = withArgCaptor {
+            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        }
+        callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+        verify(cameraTransitionCb, never())
+            .onApplyCameraProtection(any(Path::class.java), any(Rect::class.java))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
deleted file mode 100644
index 89389b0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility;
-
-import android.os.RemoteException;
-import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub {
-
-    private final CountDownLatch mCountDownLatch;
-    private final AtomicInteger mSuccessCount;
-    private final AtomicInteger mFailedCount;
-
-    MockMagnificationAnimationCallback(CountDownLatch countDownLatch) {
-        mCountDownLatch = countDownLatch;
-        mSuccessCount = new AtomicInteger();
-        mFailedCount = new AtomicInteger();
-    }
-
-    public int getSuccessCount() {
-        return mSuccessCount.get();
-    }
-
-    public int getFailedCount() {
-        return mFailedCount.get();
-    }
-
-    @Override
-    public void onResult(boolean success) throws RemoteException {
-        if (success) {
-            mSuccessCount.getAndIncrement();
-        } else {
-            mFailedCount.getAndIncrement();
-        }
-        // It should be put at the last line to avoid making CountDownLatch#await passed without
-        // updating values.
-        mCountDownLatch.countDown();
-    }
-}
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 22ebd99..284c273 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.animation.ValueAnimator;
@@ -33,7 +34,6 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -46,13 +46,15 @@
 import androidx.test.filters.LargeTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -61,22 +63,17 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 @LargeTest
 @RunWith(AndroidTestingRunner.class)
 public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
     private static final float DEFAULT_SCALE = 4.0f;
     private static final float DEFAULT_CENTER_X = 400.0f;
     private static final float DEFAULT_CENTER_Y = 500.0f;
-    // The duration and period couldn't too short, otherwise the ValueAnimator and
-    //    Instrumentation.runOnMainSync won't work in expectation. (b/288926821)
-    private static final long ANIMATION_DURATION_MS = 600;
-    private static final long WAIT_FULL_ANIMATION_PERIOD = 1000;
-    private static final long WAIT_INTERMEDIATE_ANIMATION_PERIOD = 250;
 
     private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
     private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
@@ -105,10 +102,11 @@
     private WindowMagnificationController mSpyController;
     private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
     private Instrumentation mInstrumentation;
-    private long mWaitingAnimationPeriod;
-    private long mWaitIntermediateAnimationPeriod;
+    private long mWaitAnimationDuration;
+    private long mWaitPartialAnimationDuration;
 
     private TestableWindowManager mWindowManager;
+    private ValueAnimator mValueAnimator;
 
     @Before
     public void setUp() throws Exception {
@@ -118,10 +116,14 @@
         mWindowManager = spy(new TestableWindowManager(wm));
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
 
-        mWaitingAnimationPeriod = WAIT_FULL_ANIMATION_PERIOD;
-        mWaitIntermediateAnimationPeriod = WAIT_INTERMEDIATE_ANIMATION_PERIOD;
+        // Using the animation duration in WindowMagnificationAnimationController for testing.
+        mWaitAnimationDuration = mContext.getResources()
+                .getInteger(com.android.internal.R.integer.config_longAnimTime);
+        mWaitPartialAnimationDuration = mWaitAnimationDuration / 2;
+
+        mValueAnimator = newValueAnimator();
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext, newValueAnimator());
+                mContext, mValueAnimator);
         mController = new SpyWindowMagnificationController(mContext, mHandler,
                 mWindowMagnificationAnimationController,
                 mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
@@ -131,13 +133,15 @@
 
     @After
     public void tearDown() throws Exception {
-        mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+        mInstrumentation.runOnMainSync(() -> {
+            mController.deleteWindowMagnification();
+        });
     }
 
     @Test
     public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+        enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
         verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
@@ -161,16 +165,12 @@
 
     @Test
     public void enableWindowMagnificationWithoutCallback_enabled_expectedValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+        enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
         final float targetScale = DEFAULT_SCALE + 1.0f;
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                            targetCenterX, targetCenterY, null);
-                });
+        enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
 
         verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
     }
@@ -178,14 +178,11 @@
     @Test
     public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback()
             throws RemoteException {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationAnimationController.enableWindowMagnification(1,
-                            DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
-                });
-        SystemClock.sleep(mWaitingAnimationPeriod);
+        enableWindowMagnificationAndWaitAnimating(
+                mWaitAnimationDuration, /* targetScale= */ 1.0f,
+                DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
 
-        verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X,
+        verify(mSpyController).enableWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X,
                 DEFAULT_CENTER_Y, 0f, 0f);
         verify(mAnimationCallback).onResult(true);
     }
@@ -193,23 +190,22 @@
     @Test
     public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
         final float targetScale = DEFAULT_SCALE + 1.0f;
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
+        resetMockObjects();
         mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
             mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
                     targetCenterX, targetCenterY, mAnimationCallback2);
             mCurrentScale.set(mController.getScale());
             mCurrentCenterX.set(mController.getCenterX());
             mCurrentCenterY.set(mController.getCenterY());
+            advanceTimeBy(mWaitAnimationDuration);
         });
 
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
         verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -226,24 +222,17 @@
 
     @Test
     public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
-            throws InterruptedException {
-        final CountDownLatch countDownLatch = new CountDownLatch(2);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+            throws RemoteException {
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+                mAnimationCallback);
 
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
-                animationCallback);
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
-                            Float.NaN, Float.NaN, animationCallback);
-                });
+        enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
+                Float.NaN, Float.NaN, mAnimationCallback2);
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
         // The callback in 2nd enableWindowMagnification will return true
-        assertEquals(1, animationCallback.getSuccessCount());
+        verify(mAnimationCallback2).onResult(true);
         // The callback in 1st enableWindowMagnification will return false
-        assertEquals(1, animationCallback.getFailedCount());
+        verify(mAnimationCallback).onResult(false);
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
     }
 
@@ -256,17 +245,16 @@
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
+        Mockito.reset(mSpyController);
         mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
             mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
                     targetCenterX, targetCenterY, mAnimationCallback);
             mCurrentScale.set(mController.getScale());
             mCurrentCenterX.set(mController.getCenterX());
             mCurrentCenterY.set(mController.getCenterY());
+            advanceTimeBy(mWaitAnimationDuration);
         });
 
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
         verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -293,17 +281,16 @@
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
+        Mockito.reset(mSpyController);
         mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
             mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
                     targetCenterX, targetCenterY, mAnimationCallback);
             mCurrentScale.set(mController.getScale());
             mCurrentCenterX.set(mController.getCenterX());
             mCurrentCenterY.set(mController.getCenterY());
+            advanceTimeBy(mWaitAnimationDuration);
         });
 
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
         verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -331,11 +318,8 @@
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
-        mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
-            mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                    targetCenterX, targetCenterY, null);
-        });
+        Mockito.reset(mSpyController);
+        enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
 
         verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
         assertEquals(WindowMagnificationAnimationController.STATE_DISABLED,
@@ -346,17 +330,15 @@
     public void
             enableMagnificationWithoutCallback_enabling_expectedValuesAndInvokeFormerCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
         final float targetScale = DEFAULT_SCALE - 1.0f;
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
-        mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
-            mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                    targetCenterX, targetCenterY, null);
-        });
+        Mockito.reset(mSpyController);
+        enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
+
         verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
         verify(mAnimationCallback).onResult(false);
     }
@@ -364,15 +346,12 @@
     @Test
     public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
-            mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
-                    Float.NaN, Float.NaN, mAnimationCallback2);
-        });
-        SystemClock.sleep(mWaitingAnimationPeriod);
+        Mockito.reset(mSpyController);
+        enableWindowMagnificationAndWaitAnimating(
+                mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2);
 
         verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
                 anyFloat());
@@ -383,28 +362,35 @@
     @Test
     public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationWithoutAnimation();
+        deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
         final float targetScale = DEFAULT_SCALE + 1.0f;
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                            targetCenterX, targetCenterY, mAnimationCallback2);
-                    mCurrentScale.set(mController.getScale());
-                    mCurrentCenterX.set(mController.getCenterX());
-                    mCurrentCenterY.set(mController.getCenterY());
-                });
+        Mockito.reset(mSpyController);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+                    targetCenterX, targetCenterY, mAnimationCallback2);
+            mCurrentScale.set(mController.getScale());
+            mCurrentCenterX.set(mController.getCenterX());
+            mCurrentCenterY.set(mController.getCenterY());
+        });
+
         // Current spec shouldn't match given spec.
         verify(mAnimationCallback2, never()).onResult(anyBoolean());
         verify(mAnimationCallback).onResult(false);
-        SystemClock.sleep(mWaitingAnimationPeriod);
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        mInstrumentation.runOnMainSync(() -> {
+            // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it
+            // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the
+            // animator directly to verify the result of animation is correct instead of querying
+            // the animation frame at a specific timing.
+            mValueAnimator.end();
+        });
+
+        verify(mSpyController).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -424,18 +410,14 @@
             enableMagnificationWithoutCallback_disabling_expectedValuesAndInvokeFormerCallback()
             throws RemoteException {
         enableWindowMagnificationWithoutAnimation();
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
         final float targetScale = DEFAULT_SCALE + 1.0f;
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                            targetCenterX, targetCenterY, null);
-                });
+        Mockito.reset(mSpyController);
+        enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
 
         verify(mAnimationCallback).onResult(false);
         verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
@@ -444,16 +426,13 @@
     @Test
     public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationWithoutAnimation();
+        deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
-            mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
-                    Float.NaN, Float.NaN, mAnimationCallback2);
-        });
-        SystemClock.sleep(mWaitingAnimationPeriod);
+        Mockito.reset(mSpyController);
+        enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
+                Float.NaN, Float.NaN, mAnimationCallback2);
 
         verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
                 anyFloat());
@@ -470,17 +449,16 @@
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
+        Mockito.reset(mSpyController);
         mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
             mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
                     targetCenterX, targetCenterY, mAnimationCallback2);
             mCurrentScale.set(mController.getScale());
             mCurrentCenterX.set(mController.getCenterX());
             mCurrentCenterY.set(mController.getCenterY());
+            advanceTimeBy(mWaitAnimationDuration);
         });
 
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
         verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -498,13 +476,17 @@
     public void enableWindowMagnificationWithOffset_expectedValues() {
         final float offsetRatio = -0.1f;
         final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+
+        Mockito.reset(mSpyController);
         mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
             mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
                     windowBounds.exactCenterX(), windowBounds.exactCenterY(),
                     offsetRatio, offsetRatio, mAnimationCallback);
+            advanceTimeBy(mWaitAnimationDuration);
         });
-        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        // Wait for Rects update
+        waitForIdleSync();
         final View attachedView = mWindowManager.getAttachedView();
         assertNotNull(attachedView);
         final Rect mirrorViewBound = new Rect();
@@ -516,111 +498,97 @@
                 (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
         assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
                 (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
-
     }
 
     @Test
-    public void moveWindowMagnifierToPosition_enabled_expectedValues()
-            throws InterruptedException {
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+    public void moveWindowMagnifierToPosition_enabled_expectedValues() throws RemoteException {
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
         enableWindowMagnificationWithoutAnimation();
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                    targetCenterX, targetCenterY, animationCallback);
+                    targetCenterX, targetCenterY, mAnimationCallback);
+            advanceTimeBy(mWaitAnimationDuration);
         });
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
-        assertEquals(1, animationCallback.getSuccessCount());
-        assertEquals(0, animationCallback.getFailedCount());
+        verify(mAnimationCallback).onResult(true);
+        verify(mAnimationCallback, never()).onResult(false);
         verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
     }
 
     @Test
     public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
-            throws InterruptedException {
-        final CountDownLatch countDownLatch = new CountDownLatch(4);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+            throws RemoteException {
         enableWindowMagnificationWithoutAnimation();
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                    DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
+                    DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback);
             mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                    DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
+                    DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback);
             mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                    DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
+                    DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback);
             mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                    DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
+                    DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2);
+            advanceTimeBy(mWaitAnimationDuration);
         });
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
         // only the last one callback will return true
-        assertEquals(1, animationCallback.getSuccessCount());
+        verify(mAnimationCallback2).onResult(true);
         // the others will return false
-        assertEquals(3, animationCallback.getFailedCount());
+        verify(mAnimationCallback, times(3)).onResult(false);
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
     }
 
     @Test
     public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
-            throws InterruptedException {
-        final CountDownLatch countDownLatch = new CountDownLatch(2);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+            throws RemoteException {
         final float targetCenterX = DEFAULT_CENTER_X + 100;
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
 
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
-                animationCallback);
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                            targetCenterX, targetCenterY, animationCallback);
-                });
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+                mAnimationCallback);
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    targetCenterX, targetCenterY, mAnimationCallback2);
+            advanceTimeBy(mWaitAnimationDuration);
+        });
+
         // The callback in moveWindowMagnifierToPosition will return true
-        assertEquals(1, animationCallback.getSuccessCount());
+        verify(mAnimationCallback2).onResult(true);
         // The callback in enableWindowMagnification will return false
-        assertEquals(1, animationCallback.getFailedCount());
+        verify(mAnimationCallback).onResult(false);
         verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
     }
 
     @Test
     public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
-            throws InterruptedException {
-        final CountDownLatch countDownLatch = new CountDownLatch(2);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+            throws RemoteException {
 
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
-                animationCallback);
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
-                            Float.NaN, Float.NaN, animationCallback);
-                });
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+                mAnimationCallback);
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+                    Float.NaN, Float.NaN, mAnimationCallback2);
+            advanceTimeBy(mWaitAnimationDuration);
+        });
+
         // The callback in moveWindowMagnifierToPosition will return true
-        assertEquals(1, animationCallback.getSuccessCount());
+        verify(mAnimationCallback2).onResult(true);
         // The callback in enableWindowMagnification will return false
-        assertEquals(1, animationCallback.getFailedCount());
+        verify(mAnimationCallback).onResult(false);
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
     }
 
     @Test
     public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
+        enableWindowMagnificationWithoutAnimation();
 
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+        enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
         verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
                 anyFloat());
@@ -632,7 +600,8 @@
             throws RemoteException {
         enableWindowMagnificationWithoutAnimation();
 
-        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+        resetMockObjects();
+        deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
         verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
@@ -659,7 +628,7 @@
     @Test
     public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback()
             throws RemoteException {
-        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+        deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
         Mockito.verifyNoMoreInteractions(mSpyController);
         verify(mAnimationCallback).onResult(true);
@@ -668,20 +637,25 @@
     @Test
     public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.deleteWindowMagnification(
-                            mAnimationCallback2);
-                    mCurrentScale.set(mController.getScale());
-                    mCurrentCenterX.set(mController.getCenterX());
-                    mCurrentCenterY.set(mController.getCenterY());
-                });
-        SystemClock.sleep(mWaitingAnimationPeriod);
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        Mockito.reset(mSpyController);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.deleteWindowMagnification(
+                    mAnimationCallback2);
+            mCurrentScale.set(mController.getScale());
+            mCurrentCenterX.set(mController.getCenterX());
+            mCurrentCenterY.set(mController.getCenterY());
+            // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it
+            // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the
+            // animator directly to verify the result of animation is correct instead of querying
+            // the animation frame at a specific timing.
+            mValueAnimator.end();
+        });
+
+        verify(mSpyController).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -702,14 +676,11 @@
     @Test
     public void deleteWindowMagnificationWithoutCallback_enabling_expectedValuesAndInvokeCallback()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.deleteWindowMagnification(null);
-                });
+        Mockito.reset(mSpyController);
+        deleteWindowMagnificationWithoutAnimation();
 
         verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
         verify(mAnimationCallback).onResult(false);
@@ -717,13 +688,14 @@
 
     @Test
     public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationWithoutAnimation();
+        deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2);
+        resetMockObjects();
+        deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -738,8 +710,8 @@
     @Test
     public void deleteWindowMagnificationWithoutCallback_disabling_checkStartAndValues()
             throws RemoteException {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+        enableWindowMagnificationWithoutAnimation();
+        deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
         deleteWindowMagnificationAndWaitAnimating(0, null);
@@ -757,9 +729,8 @@
         final float offsetX = 50.0f;
         final float offsetY =
                 (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
-                + 1.0f;
-        mInstrumentation.runOnMainSync(
-                () -> mController.moveWindowMagnifier(offsetX, offsetY));
+                        + 1.0f;
+        mInstrumentation.runOnMainSync(()-> mController.moveWindowMagnifier(offsetX, offsetY));
 
         verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY);
@@ -774,8 +745,8 @@
         final float offsetY =
                 (float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
                         - 1.0f;
-        mInstrumentation.runOnMainSync(
-                () -> mController.moveWindowMagnifier(offsetX, offsetY));
+        mInstrumentation.runOnMainSync(() ->
+                mController.moveWindowMagnifier(offsetX, offsetY));
 
         verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y);
@@ -790,11 +761,10 @@
                 (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE);
         // while diagonal scrolling enabled,
         //  should move with both offsetX and offsetY without regrading offsetY/offsetX
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mController.setDiagonalScrolling(true);
-                    mController.moveWindowMagnifier(offsetX, offsetY);
-                });
+        mInstrumentation.runOnMainSync(() -> {
+            mController.setDiagonalScrolling(true);
+            mController.moveWindowMagnifier(offsetX, offsetY);
+        });
 
         verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
         verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY);
@@ -806,14 +776,19 @@
         final float targetCenterY = DEFAULT_CENTER_Y + 100;
         enableWindowMagnificationWithoutAnimation();
 
-        mInstrumentation.runOnMainSync(
-                () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
-                        mAnimationCallback));
-        SystemClock.sleep(mWaitingAnimationPeriod);
+        mInstrumentation.runOnMainSync(() -> {
+            mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+                    mAnimationCallback);
+            advanceTimeBy(mWaitAnimationDuration);
+        });
 
         verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
     }
 
+    private void advanceTimeBy(long timeDelta) {
+        mAnimatorTestRule.advanceTimeBy(timeDelta);
+    }
+
     private void verifyFinalSpec(float expectedScale, float expectedCenterX,
             float expectedCenterY) {
         assertEquals(expectedScale, mController.getScale(), 0f);
@@ -822,33 +797,49 @@
     }
 
     private void enableWindowMagnificationWithoutAnimation() {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
-                            DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null);
-                });
+        enableWindowMagnificationWithoutAnimation(
+                DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    private void enableWindowMagnificationWithoutAnimation(
+            float targetScale, float targetCenterX, float targetCenterY) {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.enableWindowMagnification(
+                    targetScale, targetCenterX, targetCenterY, null);
+        });
     }
 
     private void enableWindowMagnificationAndWaitAnimating(long duration,
             @Nullable IRemoteMagnificationAnimationCallback callback) {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
-                            DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
-                });
-        SystemClock.sleep(duration);
+        enableWindowMagnificationAndWaitAnimating(
+                duration, DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
+    }
+
+    private void enableWindowMagnificationAndWaitAnimating(
+            long duration,
+            float targetScale,
+            float targetCenterX,
+            float targetCenterY,
+            @Nullable IRemoteMagnificationAnimationCallback callback) {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.enableWindowMagnification(
+                    targetScale, targetCenterX, targetCenterY, callback);
+            advanceTimeBy(duration);
+        });
+    }
+
+    private void deleteWindowMagnificationWithoutAnimation() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.deleteWindowMagnification(null);
+        });
     }
 
     private void deleteWindowMagnificationAndWaitAnimating(long duration,
             @Nullable IRemoteMagnificationAnimationCallback callback) {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    resetMockObjects();
-                    mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
-                });
-        SystemClock.sleep(duration);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
+            advanceTimeBy(duration);
+        });
     }
 
     private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
@@ -937,9 +928,9 @@
         }
     }
 
-    private static ValueAnimator newValueAnimator() {
+    private ValueAnimator newValueAnimator() {
         final ValueAnimator valueAnimator = new ValueAnimator();
-        valueAnimator.setDuration(ANIMATION_DURATION_MS);
+        valueAnimator.setDuration(mWaitAnimationDuration);
         valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
         valueAnimator.setFloatValues(0.0f, 1.0f);
         return valueAnimator;
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 b8d2bdb..06421db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -45,6 +45,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -53,6 +54,7 @@
 
 import android.animation.ValueAnimator;
 import android.annotation.IdRes;
+import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -89,6 +91,7 @@
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.FakeDisplayTracker;
@@ -102,6 +105,7 @@
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -111,8 +115,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @LargeTest
@@ -120,12 +122,10 @@
 @RunWith(AndroidTestingRunner.class)
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
-    // The duration couldn't too short, otherwise the animation check on bounce effect
-    // won't work in expectation. (b/299537784)
-    private static final int BOUNCE_EFFECT_DURATION_MS = 2000;
-    private static final long ANIMATION_DURATION_MS = 300;
-    private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
     @Mock
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
@@ -134,11 +134,16 @@
     private WindowMagnifierCallback mWindowMagnifierCallback;
     @Mock
     IRemoteMagnificationAnimationCallback mAnimationCallback;
+    @Mock
+    IRemoteMagnificationAnimationCallback mAnimationCallback2;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
     @Mock
     private SecureSettings mSecureSettings;
 
+    private long mWaitAnimationDuration;
+    private long mWaitBounceEffectDuration;
+
     private Handler mHandler;
     private TestableWindowManager mWindowManager;
     private SysUiState mSysUiState;
@@ -194,6 +199,13 @@
             mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
         }
 
+        // Using the animation duration in WindowMagnificationAnimationController for testing.
+        mWaitAnimationDuration = mResources.getInteger(
+                com.android.internal.R.integer.config_longAnimTime);
+        // Using the bounce effect duration in WindowMagnificationController for testing.
+        mWaitBounceEffectDuration = mResources.getInteger(
+                com.android.internal.R.integer.config_shortAnimTime);
+
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, mValueAnimator);
         mWindowMagnificationController =
@@ -208,7 +220,6 @@
                         mSysUiState,
                         () -> mWindowSessionSpy,
                         mSecureSettings);
-        mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS);
 
         verify(mMirrorWindowControl).setWindowDelegate(
                 any(MirrorWindowControl.MirrorWindowDelegate.class));
@@ -281,9 +292,9 @@
                     /* magnificationFrameOffsetRatioY= */ 0,
                     Mockito.mock(IRemoteMagnificationAnimationCallback.class));
         });
+        advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
 
-        verify(mSfVsyncFrameProvider,
-                timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeast(2)).postFrameCallback(any());
+        verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any());
     }
 
     @Test
@@ -401,14 +412,12 @@
 
     @Test
     public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
-            throws InterruptedException {
+            throws RemoteException {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
                     Float.NaN, 0, 0, null);
         });
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+
         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
@@ -417,12 +426,12 @@
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    targetCenterX, targetCenterY, animationCallback);
+                    targetCenterX, targetCenterY, mAnimationCallback);
         });
+        advanceTimeBy(mWaitAnimationDuration);
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
-        assertEquals(1, animationCallback.getSuccessCount());
-        assertEquals(0, animationCallback.getFailedCount());
+        verify(mAnimationCallback, times(1)).onResult(eq(true));
+        verify(mAnimationCallback, never()).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
         assertEquals(mWindowMagnificationController.getCenterX(),
@@ -435,14 +444,12 @@
 
     @Test
     public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
-            throws InterruptedException {
+            throws RemoteException {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
                     Float.NaN, 0, 0, null);
         });
-        final CountDownLatch countDownLatch = new CountDownLatch(4);
-        final MockMagnificationAnimationCallback animationCallback =
-                new MockMagnificationAnimationCallback(countDownLatch);
+
         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
@@ -451,20 +458,20 @@
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 10, centerY + 10, animationCallback);
+                    centerX + 10, centerY + 10, mAnimationCallback);
             mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 20, centerY + 20, animationCallback);
+                    centerX + 20, centerY + 20, mAnimationCallback);
             mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 30, centerY + 30, animationCallback);
+                    centerX + 30, centerY + 30, mAnimationCallback);
             mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 40, centerY + 40, animationCallback);
+                    centerX + 40, centerY + 40, mAnimationCallback2);
         });
+        advanceTimeBy(mWaitAnimationDuration);
 
-        assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
         // only the last one callback will return true
-        assertEquals(1, animationCallback.getSuccessCount());
+        verify(mAnimationCallback2).onResult(eq(true));
         // the others will return false
-        assertEquals(3, animationCallback.getFailedCount());
+        verify(mAnimationCallback, times(3)).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
         assertEquals(mWindowMagnificationController.getCenterX(),
@@ -1078,27 +1085,16 @@
 
         final View mirrorView = mWindowManager.getAttachedView();
 
-        final long timeout = SystemClock.uptimeMillis() + 5000;
         final AtomicDouble maxScaleX = new AtomicDouble();
-        final Runnable onAnimationFrame = new Runnable() {
-            @Override
-            public void run() {
-                // For some reason the fancy way doesn't compile...
-//                maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
-                final double oldMax = maxScaleX.get();
-                final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-                assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+        advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
+            // For some reason the fancy way doesn't compile...
+            // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
+            final double oldMax = maxScaleX.get();
+            final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
+            assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+        });
 
-                if (SystemClock.uptimeMillis() < timeout) {
-                    mirrorView.postOnAnimation(this);
-                }
-            }
-        };
-        mirrorView.postOnAnimation(onAnimationFrame);
-
-        waitForIdleSync();
-
-        ReferenceTestUtils.waitForCondition(() -> maxScaleX.get() > 1.0);
+        assertTrue(maxScaleX.get() > 1.0);
     }
 
     @Test
@@ -1278,7 +1274,8 @@
 
         final float magnificationScaleLarge = 2.5f;
         final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
-        final int magnificationSize = (int) (initSize * magnificationScaleLarge);
+        final int magnificationSize = (int) (initSize * magnificationScaleLarge)
+                - (int) (initSize * magnificationScaleLarge) % 2;
 
         final int expectedWindowHeight = magnificationSize;
         final int expectedWindowWidth = magnificationSize;
@@ -1454,4 +1451,23 @@
         return newRotation;
     }
 
+    // advance time based on the device frame refresh rate
+    private void advanceTimeBy(long timeDelta) {
+        advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
+    }
+
+    // advance time based on the device frame refresh rate, and trigger runnable on each refresh
+    private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
+        final float frameRate = mContext.getDisplay().getRefreshRate();
+        final int timeSlot = (int) (1000 / frameRate);
+        int round = (int) Math.ceil((double) timeDelta / timeSlot);
+        for (; round >= 0; round--) {
+            mInstrumentation.runOnMainSync(() -> {
+                mAnimatorTestRule.advanceTimeBy(timeSlot);
+                if (runnableOnEachRefresh != null) {
+                    runnableOnEachRefresh.run();
+                }
+            });
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..daa6070 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,6 +19,8 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -87,6 +89,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         MockitoAnnotations.initMocks(this);
         mContextWrapper = new ContextWrapper(mContext) {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index fd258e3..5666435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -56,6 +58,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mock(SecureSettings.class));
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 3a8bcd0..e832940 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
@@ -75,6 +77,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
@@ -96,6 +99,7 @@
         Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
                 mLastIsMoveToTucked);
         mEndListenerCaptor.getAllValues().clear();
+        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
     }
 
     @Test
@@ -230,10 +234,12 @@
 
         mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_X, new SpringForce()
                 .setStiffness(stiffness)
-                .setDampingRatio(dampingRatio), velocity, finalPosition);
+                .setDampingRatio(dampingRatio), velocity, finalPosition,
+                /* writeToPosition = */ true);
         mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_Y, new SpringForce()
                 .setStiffness(stiffness)
-                .setDampingRatio(dampingRatio), velocity, finalPosition);
+                .setDampingRatio(dampingRatio), velocity, finalPosition,
+                /* writeToPosition = */ true);
     }
 
     private void skipAnimationToEnd(DynamicAnimation animation) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index 9b81947..e8192c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.res.Resources;
@@ -26,8 +28,8 @@
 
 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;
@@ -43,6 +45,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
         mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
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 5764839..62cb9a0 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
@@ -19,6 +19,8 @@
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
@@ -37,8 +39,8 @@
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
@@ -72,6 +74,7 @@
 
     @Before
     public void setUp() {
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
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 98be49f..3248753 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
@@ -18,6 +18,8 @@
 
 import static android.view.View.OVER_SCROLL_NEVER;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -33,6 +35,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.SmallTest;
 
@@ -79,6 +82,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mock(SecureSettings.class));
@@ -213,5 +217,6 @@
     @After
     public void tearDown() {
         mMotionEventHelper.recycleEvents();
+        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 31824ec..03a4ba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -19,6 +19,8 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.systemBars;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
@@ -73,6 +75,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
                 mWindowManager).getMaximumWindowMetrics();
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 5bb5e01..76094c1 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
@@ -23,6 +23,7 @@
 import static android.view.WindowInsets.Type.systemBars;
 
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,6 +42,10 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.UserHandle;
+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.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -50,8 +55,11 @@
 import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -96,6 +104,10 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
 
@@ -110,10 +122,12 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         final Rect mDisplayBounds = new Rect();
         mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
                 DISPLAY_WINDOW_HEIGHT);
-        mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets()));
+        mWindowMetrics = spy(
+                new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
         doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
 
         mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
@@ -145,6 +159,7 @@
                 UserHandle.USER_CURRENT);
 
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
         mMenuViewLayer.onDetachedFromWindow();
     }
 
@@ -217,27 +232,62 @@
     }
 
     @Test
-    public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
-        final float menuTop = IME_TOP + 100;
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+    @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
+        final PointF beforePosition = mMenuView.getMenuPosition();
 
         dispatchShowingImeInsets();
 
         final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
-        assertThat(mMenuView.getTranslationX()).isEqualTo(0);
-        assertThat(menuBottom).isLessThan(IME_TOP);
+        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+        assertThat(menuBottom).isLessThan(beforePosition.y);
     }
 
     @Test
-    public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
-        final float menuTop = IME_TOP + 200;
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
+        final PointF beforePosition = mMenuView.getMenuPosition();
+
         dispatchShowingImeInsets();
+        assertThat(isPositionAnimationRunning()).isTrue();
+        skipPositionAnimations();
+
+        final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
+
+        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+        assertThat(menuBottom).isLessThan(beforePosition.y);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
+        final PointF beforePosition = mMenuView.getMenuPosition();
 
         dispatchHidingImeInsets();
 
-        assertThat(mMenuView.getTranslationX()).isEqualTo(0);
-        assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
+        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+        assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
+        final PointF beforePosition = mMenuView.getMenuPosition();
+
+        dispatchShowingImeInsets();
+        assertThat(isPositionAnimationRunning()).isTrue();
+        skipPositionAnimations();
+
+        dispatchHidingImeInsets();
+        assertThat(isPositionAnimationRunning()).isTrue();
+        skipPositionAnimations();
+
+        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+        assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
     }
 
     private void setupEnabledAccessibilityServiceList() {
@@ -290,4 +340,21 @@
                         Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom))
                 .build();
     }
+
+    private boolean isPositionAnimationRunning() {
+        return !mMenuAnimationController.mPositionAnimations.values().stream().filter(
+                        (animation) -> animation.isRunning()).findAny().isEmpty();
+    }
+
+    private void skipPositionAnimations() {
+        mMenuAnimationController.mPositionAnimations.values().stream().forEach(
+                (animation) -> {
+                    final SpringAnimation springAnimation = ((SpringAnimation) animation);
+                    // The doAnimationFrame function is used for skipping animation to the end.
+                    springAnimation.doAnimationFrame(500);
+                    springAnimation.skipToEnd();
+                    springAnimation.doAnimationFrame(500);
+                });
+
+    }
 }
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 5cd0fd0..b9fd5d0f 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
@@ -18,6 +18,8 @@
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -67,6 +69,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefaults(mSetFlagsRule);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mNightMode = mUiModeManager.getNightMode();
         mUiModeManager.setNightMode(MODE_NIGHT_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java
new file mode 100644
index 0000000..c7bb0f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.utils;
+
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.systemui.Flags;
+
+public class FlagUtils {
+    /**
+     * Populates a setFlagsRule with every SystemUI a11y feature flag.
+     * This function should be updated when new flags are added.
+     *
+     * @param setFlagsRule set flags rule from the test environment.
+     */
+    public static void setFlagDefaults(SetFlagsRule setFlagsRule) {
+        setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index a7e7dd0..2b51ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @RunWithLooper
+@FlakyTest(bugId = 302149604)
 class AnimatorTestRuleOrderTest : SysuiTestCase() {
 
     @get:Rule val animatorTestRule = AnimatorTestRule()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
deleted file mode 100644
index 215d635..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ /dev/null
@@ -1,101 +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.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.SensorProperties
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.view.ViewGroup.LayoutParams
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var iconView: LottieAnimationView
-    @Mock private lateinit var iconViewOverlay: LottieAnimationView
-    @Mock private lateinit var layoutParam: LayoutParams
-    @Mock private lateinit var fingerprintManager: FingerprintManager
-
-    private lateinit var controller: AuthBiometricFingerprintIconController
-
-    @Before
-    fun setUp() {
-        context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
-        whenEver(iconView.layoutParams).thenReturn(layoutParam)
-        whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
-    }
-
-    @Test
-    fun testIconContentDescription_SfpsDevice() {
-        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
-        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
-        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
-            .isEqualTo(
-                context.resources.getString(
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                )
-            )
-    }
-
-    @Test
-    fun testIconContentDescription_NonSfpsDevice() {
-        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
-        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
-        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
-            .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
-    }
-
-    private fun setupFingerprintSensorProperties(sensorType: Int) {
-        whenEver(fingerprintManager.sensorPropertiesInternal)
-            .thenReturn(
-                listOf(
-                    FingerprintSensorPropertiesInternal(
-                        SENSOR_ID,
-                        SensorProperties.STRENGTH_STRONG,
-                        5 /* maxEnrollmentsPerUser */,
-                        listOf() /* componentInfo */,
-                        sensorType,
-                        true /* halControlsIllumination */,
-                        true /* resetLockoutRequiresHardwareAuthToken */,
-                        listOf() /* sensorLocations */
-                    )
-                )
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index d68a313..8c26776 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -72,6 +72,7 @@
             runCurrent()
 
             // WHEN shade expands
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
@@ -108,6 +109,7 @@
 
             // WHEN detector is disabled and shade opens
             detector.disable()
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 9f24a9f..15633d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -30,6 +30,7 @@
 internal fun fingerprintSensorPropertiesInternal(
     ids: List<Int> = listOf(0),
     strong: Boolean = true,
+    sensorType: Int = FingerprintSensorProperties.TYPE_REAR
 ): List<FingerprintSensorPropertiesInternal> {
     val componentInfo =
         listOf(
@@ -54,7 +55,7 @@
             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
             5 /* maxEnrollmentsPerUser */,
             componentInfo,
-            FingerprintSensorProperties.TYPE_REAR,
+            sensorType,
             false /* resetLockoutRequiresHardwareAuthToken */
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index e9e9624..c5f16aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -29,7 +29,6 @@
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.Surface
-import android.view.Surface.ROTATION_0
 import android.view.Surface.Rotation
 import android.view.View
 import android.view.WindowManager
@@ -37,7 +36,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -45,15 +43,16 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -113,6 +112,7 @@
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
             UdfpsKeyguardAccessibilityDelegate
     @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
@@ -141,7 +141,6 @@
     ) {
         controllerOverlay = UdfpsControllerOverlay(
             context,
-            fingerprintManager,
             inflater,
             windowManager,
             accessibilityManager,
@@ -155,7 +154,6 @@
             keyguardStateController,
             unlockedScreenOffAnimationController,
             udfpsDisplayMode,
-            secureSettings,
             REQUEST_ID,
             reason,
             controllerCallback,
@@ -165,9 +163,9 @@
             primaryBouncerInteractor,
             alternateBouncerInteractor,
             isDebuggable,
-            udfpsUtils,
             udfpsKeyguardAccessibilityDelegate,
             udfpsKeyguardViewModels,
+            mSelectedUserInteractor,
         )
         block()
     }
@@ -212,8 +210,8 @@
             val lp = layoutParamsCaptor.value
             assertThat(lp.x).isEqualTo(0)
             assertThat(lp.y).isEqualTo(0)
-            assertThat(lp.width).isEqualTo(SENSOR_WIDTH)
-            assertThat(lp.height).isEqualTo(SENSOR_HEIGHT)
+            assertThat(lp.width).isEqualTo(DISPLAY_WIDTH)
+            assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT)
         }
     }
 
@@ -230,8 +228,8 @@
             val lp = layoutParamsCaptor.value
             assertThat(lp.x).isEqualTo(0)
             assertThat(lp.y).isEqualTo(0)
-            assertThat(lp.width).isEqualTo(SENSOR_WIDTH)
-            assertThat(lp.height).isEqualTo(SENSOR_HEIGHT)
+            assertThat(lp.width).isEqualTo(DISPLAY_WIDTH)
+            assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT)
         }
     }
 
@@ -247,9 +245,9 @@
             // Sensor should be in the bottom left corner in ROTATION_90.
             val lp = layoutParamsCaptor.value
             assertThat(lp.x).isEqualTo(0)
-            assertThat(lp.y).isEqualTo(DISPLAY_WIDTH - SENSOR_WIDTH)
-            assertThat(lp.width).isEqualTo(SENSOR_HEIGHT)
-            assertThat(lp.height).isEqualTo(SENSOR_WIDTH)
+            assertThat(lp.y).isEqualTo(0)
+            assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT)
+            assertThat(lp.height).isEqualTo(DISPLAY_WIDTH)
         }
     }
 
@@ -264,10 +262,10 @@
 
             // Sensor should be in the top right corner in ROTATION_270.
             val lp = layoutParamsCaptor.value
-            assertThat(lp.x).isEqualTo(DISPLAY_HEIGHT - SENSOR_HEIGHT)
+            assertThat(lp.x).isEqualTo(0)
             assertThat(lp.y).isEqualTo(0)
-            assertThat(lp.width).isEqualTo(SENSOR_HEIGHT)
-            assertThat(lp.height).isEqualTo(SENSOR_WIDTH)
+            assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT)
+            assertThat(lp.height).isEqualTo(DISPLAY_WIDTH)
         }
     }
 
@@ -345,11 +343,10 @@
     }
 
     @Test
-    fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) {
+    fun smallOverlayOnEnrollmentWithA11y() = withRotation(Surface.ROTATION_0) {
         withReason(REASON_ENROLL_ENROLLING) {
             // When a11y enabled during enrollment
             whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)
-            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
 
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
@@ -363,22 +360,4 @@
             assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
         }
     }
-
-    @Test
-    fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) {
-        withReason(REASON_AUTH_KEYGUARD) {
-            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
-
-            controllerOverlay.show(udfpsController, overlayParams)
-            verify(windowManager).addView(
-                    eq(controllerOverlay.overlayView),
-                    layoutParamsCaptor.capture()
-            )
-
-            // Layout params should use natural display width and height
-            val lp = layoutParamsCaptor.value
-            assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth)
-            assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight)
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a36f4e9..f32e1a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -88,7 +88,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
@@ -103,9 +102,9 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 
@@ -122,7 +121,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 import javax.inject.Provider;
 
@@ -206,8 +204,6 @@
     @Mock
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
-    private AlternateUdfpsTouchProvider mAlternateTouchProvider;
-    @Mock
     private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock
     private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
@@ -216,11 +212,11 @@
     @Mock
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
     private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @Mock
     private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     // Capture listeners so that they can be used to send events
     @Captor
@@ -239,7 +235,6 @@
     private ScreenLifecycle.Observer mScreenObserver;
     private FingerprintSensorPropertiesInternal mOpticalProps;
     private FingerprintSensorPropertiesInternal mUltrasonicProps;
-    private UdfpsUtils mUdfpsUtils;
     @Mock
     private InputManager mInputManager;
     @Mock
@@ -250,8 +245,6 @@
         mContext.getOrCreateTestableResources()
                 .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false);
 
-        mUdfpsUtils = new UdfpsUtils();
-
         when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
                 .thenReturn(mUdfpsView);
         when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view_legacy, null))
@@ -292,24 +285,13 @@
         // Create a fake background executor.
         mBiometricExecutor = new FakeExecutor(new FakeSystemClock());
 
-        initUdfpsController(true /* hasAlternateTouchProvider */);
+        initUdfpsController(mOpticalProps);
     }
 
-
-    private void initUdfpsController(boolean hasAlternateTouchProvider) {
-        initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
-    }
-
-    private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps,
-            boolean hasAlternateTouchProvider) {
+    private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps) {
         reset(mFingerprintManager);
         reset(mScreenLifecycle);
 
-        final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider =
-                hasAlternateTouchProvider ? Optional.of(
-                        (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
-                        : Optional.empty();
-
         mUdfpsController = new UdfpsController(
                 mContext,
                 new FakeExecution(),
@@ -339,18 +321,16 @@
                 mSystemUIDialogManager,
                 mLatencyTracker,
                 mActivityLaunchAnimator,
-                alternateTouchProvider,
                 mBiometricExecutor,
                 mPrimaryBouncerInteractor,
                 mSinglePointerTouchProcessor,
                 mSessionTracker,
                 mAlternateBouncerInteractor,
-                mSecureSettings,
                 mInputManager,
-                mUdfpsUtils,
                 mock(KeyguardFaceAuthInteractor.class),
                 mUdfpsKeyguardAccessibilityDelegate,
-                mUdfpsKeyguardViewModels
+                mUdfpsKeyguardViewModels,
+                mSelectedUserInteractor
         );
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
@@ -374,17 +354,15 @@
     public void onActionDownTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException {
         // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
+        MotionEvent downEvent = obtainMotionEvent(ACTION_DOWN, 0, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
@@ -408,16 +386,14 @@
             throws RemoteException {
         // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_MOVE is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
         if (stale) {
             mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
@@ -436,22 +412,22 @@
     public void onMultipleTouch_whenCanDismissLockScreen_entersDeviceOnce() throws RemoteException {
         // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false);
 
-        // WHEN multiple touches are received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        // GIVEN that the overlay is showing
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
+
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.second);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricExecutor.runAllReady();
         moveEvent.recycle();
@@ -593,22 +569,17 @@
 
     private static class TestParams {
         public final FingerprintSensorPropertiesInternal sensorProps;
-        public final boolean hasAlternateTouchProvider;
 
-        TestParams(FingerprintSensorPropertiesInternal sensorProps,
-                boolean hasAlternateTouchProvider) {
+        TestParams(FingerprintSensorPropertiesInternal sensorProps) {
             this.sensorProps = sensorProps;
-            this.hasAlternateTouchProvider = hasAlternateTouchProvider;
         }
     }
 
     private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) {
         for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
                 mUltrasonicProps)) {
-            for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) {
-                initUdfpsController(sensorProps, hasAlternateTouchProvider);
-                testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider));
-            }
+            initUdfpsController(sensorProps);
+            testParamsConsumer.accept(new TestParams(sensorProps));
         }
     }
 
@@ -621,23 +592,33 @@
     private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized(
             TestParams testParams) throws RemoteException {
         reset(mUdfpsView);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner.
+        final int pointerId = 0;
         final int displayWidth = 1080;
         final int displayHeight = 1920;
-        final float scaleFactor = 0.75f; // This means the native resolution is 1440x2560.
+        final float scaleFactor = 1f; // This means the native resolution is 1440x2560.
         final float touchMinor = 10f;
         final float touchMajor = 20f;
+        final float orientation = 30f;
 
         // Expecting a touch at the very bottom right corner in native orientation and resolution.
-        final int expectedX = (int) (displayWidth / scaleFactor);
-        final int expectedY = (int) (displayHeight / scaleFactor);
+        final float expectedX = displayWidth / scaleFactor;
+        final float expectedY = displayHeight / scaleFactor;
         final float expectedMinor = touchMinor / scaleFactor;
         final float expectedMajor = touchMajor / scaleFactor;
 
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN a valid touch on sensor
+        NormalizedTouchData touchData = new NormalizedTouchData(pointerId, displayWidth,
+                displayHeight, touchMinor, touchMajor, orientation, 0L, 0L);
+        TouchProcessorResult processorDownResult = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.DOWN, 1, touchData);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorDownResult);
 
         // Show the overlay.
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
@@ -654,21 +635,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
 
         // Test ROTATION_90
-        reset(mAlternateTouchProvider);
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
@@ -677,21 +649,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, displayHeight, 0, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
 
         // Test ROTATION_270
-        reset(mAlternateTouchProvider);
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
@@ -700,21 +663,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, 0, displayWidth, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
 
         // Test ROTATION_180
-        reset(mAlternateTouchProvider);
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
@@ -724,18 +678,10 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
     }
 
     @Test
@@ -744,46 +690,36 @@
     }
 
     private void fingerDownParameterized(TestParams testParams) throws RemoteException {
-        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
+        reset(mUdfpsView, mFingerprintManager, mLatencyTracker,
                 mKeyguardUpdateMonitor);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
-        // GIVEN that the overlay is showing
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+
+        initUdfpsController(testParams.sensorProps);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
 
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
-
-        mFgExecutor.runAllReady();
-
         // THEN the touch provider is notified about onPointerDown.
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
-                    eq(0f));
-            verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                    anyInt(), anyFloat(), anyFloat());
-            verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f));
-            verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(),
-                    anyFloat(), anyFloat());
-        }
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
 
         // AND display configuration begins
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
@@ -800,33 +736,20 @@
             // AND onDisplayConfigured notifies FingerprintManager about onUiReady
             mOnDisplayConfiguredCaptor.getValue().run();
             mBiometricExecutor.runAllReady();
-            if (testParams.hasAlternateTouchProvider) {
-                InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
-                inOrder.verify(mAlternateTouchProvider).onUiReady();
-                inOrder.verify(mLatencyTracker).onActionEnd(
-                        eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
-                verify(mFingerprintManager, never()).onUdfpsUiEvent(
-                        eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
-            } else {
-                InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
-                inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
-                        eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
-                        eq(testParams.sensorProps.sensorId));
-                inOrder.verify(mLatencyTracker).onActionEnd(
-                        eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
-                verify(mAlternateTouchProvider, never()).onUiReady();
-            }
+            InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
+            inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
+                    eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId));
+            inOrder.verify(mLatencyTracker).onActionEnd(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         } else {
             verify(mFingerprintManager, never()).onUdfpsUiEvent(
                     eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
-            verify(mAlternateTouchProvider, never()).onUiReady();
             verify(mLatencyTracker, never()).onActionEnd(
                     eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         }
     }
 
-
-
     @Test
     public void aodInterrupt() {
         runWithAllParams(this::aodInterruptParameterized);
@@ -834,8 +757,9 @@
 
     private void aodInterruptParameterized(TestParams testParams) throws RemoteException {
         mUdfpsController.cancelAodSendFingerUpAction();
-        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
+        reset(mUdfpsView, mFingerprintManager, mKeyguardUpdateMonitor);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         // GIVEN that the overlay is showing and screen is on and fp is running
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
@@ -855,19 +779,8 @@
         }
         mBiometricExecutor.runAllReady();
 
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0),
-                    eq(3f) /* minor */, eq(2f) /* major */);
-            verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                    anyInt(), anyFloat(), anyFloat());
-            verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */,
-                    eq(2f) /* major */);
-            verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                    anyFloat(), anyFloat());
-        }
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
     }
 
     @Test
@@ -907,10 +820,12 @@
     private void onFingerUp_displayConfigurationParameterized(TestParams testParams)
             throws RemoteException {
         reset(mUdfpsView);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
+
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
@@ -918,7 +833,8 @@
             when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
 
             // WHEN up-action received
-            verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+            when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                    touchProcessorResult.second);
             MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
             mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
             mBiometricExecutor.runAllReady();
@@ -931,7 +847,8 @@
             when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
 
             // WHEN up-action received
-            verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+            when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                    touchProcessorResult.second);
             MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
             mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
             mBiometricExecutor.runAllReady();
@@ -1015,16 +932,19 @@
     private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams)
             throws RemoteException {
         reset(mUdfpsView);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
 
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
             // Configure UdfpsView to accept the ACTION_UP event
             when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
@@ -1033,7 +953,8 @@
         }
 
         // WHEN ACTION_UP is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.second);
         MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
         mBiometricExecutor.runAllReady();
@@ -1043,16 +964,13 @@
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
 
         // WHEN ACTION_DOWN is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
 
-        // WHEN ACTION_MOVE is received
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
         mFgExecutor.runAllReady();
 
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
@@ -1122,24 +1040,16 @@
 
     @Test
     public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException {
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true);
 
         // WHEN ACTION_HOVER is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
         MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
         mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
         enterEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
-        mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
-        moveEvent.recycle();
 
         // THEN tick haptic is played
         verify(mVibrator).vibrate(
@@ -1159,24 +1069,16 @@
     public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled()
             throws RemoteException {
         when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
-        // GIVEN that the overlay is showing and a11y touch exploration enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true);
 
         // WHEN ACTION_HOVER is received
-        verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
         mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
         enterEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
-        mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
-        moveEvent.recycle();
 
         // THEN context click haptic is played
         verify(mVibrator).performHapticFeedback(
@@ -1187,26 +1089,16 @@
 
     @Test
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
 
         // THEN NO haptic played
         verify(mVibrator, never()).vibrate(
@@ -1221,80 +1113,26 @@
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled()
             throws RemoteException {
         when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
 
         // THEN NO haptic played
         verify(mVibrator, never()).performHapticFeedback(any(), anyInt());
     }
 
     @Test
-    public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
-            throws RemoteException {
-        // Disable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-
-        // WHEN ACTION_DOWN is received
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
-        mBiometricExecutor.runAllReady();
-        downEvent.recycle();
-
-        // AND ACTION_MOVE is received
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
-
-        // AND ACTION_UP is received
-        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
-        mBiometricExecutor.runAllReady();
-        upEvent.recycle();
-
-        // THEN the old FingerprintManager path is invoked.
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
-                anyFloat(), anyFloat());
-        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt());
-    }
-
-    @Test
     public void fingerDown_falsingManagerInformed() throws RemoteException {
         final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
-                givenAcceptFingerDownEvent();
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
@@ -1308,85 +1146,46 @@
         verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION);
     }
 
-    @Test
-    public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
-            throws RemoteException {
-        final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp =
-                givenAcceptFingerDownEvent();
-
-        // WHEN ACTION_DOWN is received
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDownAndUp.first);
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
-        mBiometricExecutor.runAllReady();
-        downEvent.recycle();
-
-        // AND ACTION_UP is received
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDownAndUp.second);
-        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
-        mBiometricExecutor.runAllReady();
-        upEvent.recycle();
-
-        // THEN the new FingerprintManager path is invoked.
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-    }
-
-    private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent()
+    private Pair<TouchProcessorResult, TouchProcessorResult> givenFingerEvent(
+            InteractionEvent event1, InteractionEvent event2, boolean a11y)
             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);
+                event1, 1 /* pointerId */, touchData);
         final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch(
-                InteractionEvent.UP, 1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+                event2, 1 /* pointerId */, touchData);
 
         // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+        initUdfpsController(mOpticalProps);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        if (a11y) {
+            verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+        } else {
+            verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        }
 
         return new Pair<>(processorResultDown, processorResultUp);
     }
 
     @Test
-    public void onTouch_WithNewTouchDetection_forwardToKeyguard() throws RemoteException {
+    public void onTouch_forwardToKeyguard() throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
         final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
                 InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
-
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
@@ -1402,47 +1201,23 @@
 
         // THEN the touch is forwarded to Keyguard
         verify(mStatusBarKeyguardViewManager).onTouch(downEvent);
-        downEvent.recycle();
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_pilferPointer() 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);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+    public void onTouch_pilferPointer() throws RemoteException {
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false);
 
         // WHEN ACTION_DOWN is received
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDown);
+                touchProcessorResult.first);
         MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
 
         // WHEN ACTION_MOVE is received after
-        final TouchProcessorResult processorResultUnchanged =
-                new TouchProcessorResult.ProcessedTouch(
-                        InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultUnchanged);
+                touchProcessorResult.second);
         event.setAction(ACTION_MOVE);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
@@ -1453,25 +1228,13 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException {
+    public void onTouch_doNotPilferPointer() throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
         final TouchProcessorResult processorResultUnchanged =
                 new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
-                        1 /* pointerId */, touchData);
+                        -1 /* pointerId */, touchData);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to not accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
@@ -1491,36 +1254,17 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing()
+    public void onTouch_pilferPointerWhenAltBouncerShowing()
             throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultUnchanged =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
-                        1 /* pointerId */, touchData);
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to not accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
-
-        // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        // WHEN alternate bouncer is showing
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // WHEN ACTION_DOWN is received and touch is not within sensor
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultUnchanged);
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
@@ -1531,32 +1275,10 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer()
+    public void onTouch_doNotProcessTouchWhenPullingUpBouncer()
             throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultMove =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_MOVE event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false);
 
         // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS
         when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
@@ -1564,7 +1286,7 @@
 
         // WHEN ACTION_MOVE is received and touch is within sensor
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultMove);
+                touchProcessorResult.first);
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricExecutor.runAllReady();
@@ -1578,40 +1300,19 @@
 
 
     @Test
-    public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible()
+    public void onTouch_qsDrag_processesTouchWhenAlternateBouncerVisible()
             throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultMove =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_MOVE event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-
         // GIVEN swipe down for QS
         when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false);
         when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f);
 
         // WHEN ACTION_MOVE is received and touch is within sensor
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultMove);
+                touchProcessorResult.first);
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricExecutor.runAllReady();
@@ -1689,43 +1390,4 @@
         // THEN vibrate is used
         verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
     }
-
-    @Test
-    public void aodInterrupt_withNewTouchDetection() throws RemoteException {
-        mUdfpsController.cancelAodSendFingerUpAction();
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultDown =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // GIVEN that the overlay is showing and screen is on and fp is running
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mScreenObserver.onScreenTurnedOn();
-        mFgExecutor.runAllReady();
-
-        // WHEN fingerprint is requested because of AOD interrupt
-        mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
-
-        // Check case where touch driver sends touch to UdfpsView as well
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDown);
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
-
-        mBiometricExecutor.runAllReady();
-
-        // THEN only one onPointerDown is sent
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 3276e66..2c4e136 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -30,7 +30,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -41,6 +40,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.junit.Before;
@@ -70,6 +70,7 @@
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
     protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    protected @Mock SelectedUserInteractor mSelectedUserInteractor;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -116,7 +117,7 @@
     }
 
     public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() {
-        return createUdfpsKeyguardViewController(false, false);
+        return createUdfpsKeyguardViewController(false);
     }
 
     public void captureKeyGuardViewManagerCallback() {
@@ -126,8 +127,7 @@
     }
 
     protected UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController(
-            boolean useModernBouncer, boolean useExpandedOverlay) {
-        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
+            boolean useModernBouncer) {
         UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy(
                 mView,
                 mStatusBarStateController,
@@ -144,7 +144,8 @@
                 mFeatureFlags,
                 mPrimaryBouncerInteractor,
                 mAlternateBouncerInteractor,
-                mUdfpsKeyguardAccessibilityDelegate);
+                mUdfpsKeyguardAccessibilityDelegate,
+                mSelectedUserInteractor);
         return controller;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
index b018a3e..21928cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
@@ -18,15 +18,12 @@
 
 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.Mockito.atLeast;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.TestableLooper;
-import android.view.MotionEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -44,8 +41,7 @@
         UdfpsKeyguardViewLegacyControllerBaseTest {
     @Override
     public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() {
-        return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
-                /* useExpandedOverlay */ false);
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
     }
 
     @Test
@@ -216,37 +212,4 @@
         sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
         assertTrue(mController.shouldPauseAuth());
     }
-
-    @Test
-    // TODO(b/259264861): Tracking Bug
-    public void testUdfpsExpandedOverlayOn() {
-        // GIVEN view is attached and useExpandedOverlay is true
-        mController = createUdfpsKeyguardViewController(false, true);
-        mController.onViewAttached();
-        captureKeyGuardViewManagerCallback();
-
-        // WHEN a touch is received
-        mKeyguardViewManagerCallback.onTouch(
-                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
-
-        // THEN udfpsController onTouch is not called
-        assertTrue(mView.mUseExpandedOverlay);
-        verify(mUdfpsController, never()).onTouch(any());
-    }
-
-    @Test
-    // TODO(b/259264861): Tracking Bug
-    public void testUdfpsExpandedOverlayOff() {
-        // GIVEN view is attached and useExpandedOverlay is false
-        mController.onViewAttached();
-        captureKeyGuardViewManagerCallback();
-
-        // WHEN a touch is received
-        mKeyguardViewManagerCallback.onTouch(
-                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
-
-        // THEN udfpsController onTouch is called
-        assertFalse(mView.mUseExpandedOverlay);
-        verify(mUdfpsController).onTouch(any());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index da4548b..97dada2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -96,6 +96,7 @@
                 mKeyguardUpdateMonitor,
                 FakeTrustRepository(),
                 testScope.backgroundScope,
+                mSelectedUserInteractor,
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -106,10 +107,7 @@
                 mock(SystemClock::class.java),
                 mKeyguardUpdateMonitor,
             )
-        return createUdfpsKeyguardViewController(
-            /* useModernBouncer */ true, /* useExpandedOverlay */
-            false
-        )
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 0c8e7a5..9fbe096 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.biometrics
 
-import android.graphics.PointF
-import android.graphics.RectF
 import android.hardware.biometrics.SensorLocationInternal
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -42,7 +40,6 @@
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
-import org.mockito.Mockito.`when` as whenever
 
 private const val SENSOR_X = 50
 private const val SENSOR_Y = 250
@@ -80,56 +77,7 @@
         ViewUtils.detachView(view)
     }
 
-    @Test
-    fun layoutSizeFitsSensor() {
-        val params = withArgCaptor<RectF> {
-            verify(animationViewController).onSensorRectUpdated(capture())
-        }
-        assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS)
-        assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS)
-    }
-
-    @Test
-    fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true)
-
-    @Test
-    fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false)
-
-    private fun isWithinSensorArea(paused: Boolean) {
-        whenever(animationViewController.shouldPauseAuth()).thenReturn(paused)
-        whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
-        val end = (SENSOR_RADIUS * 2) - 1
-        for (x in 1 until end) {
-            for (y in 1 until end) {
-                assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused)
-            }
-        }
-    }
-
-    @Test
-    fun isWithinSensorAreaWhenTranslated() {
-        val offset = PointF(100f, 200f)
-        whenever(animationViewController.touchTranslation).thenReturn(offset)
-        val end = (SENSOR_RADIUS * 2) - 1
-        for (x in 0 until offset.x.toInt() step 2) {
-            for (y in 0 until offset.y.toInt() step 2) {
-                assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse()
-            }
-        }
-        for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) {
-            for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) {
-                assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue()
-            }
-        }
-    }
-
-    @Test
-    fun isNotWithinSensorArea() {
-        whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
-        assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat()))
-            .isFalse()
-        assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
-    }
+    // TODO: Add test to verify view is size of screen
 
     @Test
     fun startAndStopIllumination() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
deleted file mode 100644
index 712eef1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.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.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.junit.MockitoJUnit
-
-@SmallTest
-@RunWith(JUnit4::class)
-class SideFpsOverlayInteractorTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-    private lateinit var testScope: TestScope
-
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-
-    private lateinit var interactor: SideFpsOverlayInteractor
-
-    @Before
-    fun setup() {
-        testScope = TestScope(StandardTestDispatcher())
-        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
-    }
-
-    @Test
-    fun testOverlayOffsetUpdates() =
-        testScope.runTest {
-            fingerprintRepository.setProperties(
-                sensorId = 1,
-                strength = SensorStrength.STRONG,
-                sensorType = FingerprintSensorType.REAR,
-                sensorLocations =
-                    mapOf(
-                        "" to
-                            SensorLocationInternal(
-                                "" /* displayId */,
-                                540 /* sensorLocationX */,
-                                1636 /* sensorLocationY */,
-                                130 /* sensorRadius */
-                            ),
-                        "display_id_1" to
-                            SensorLocationInternal(
-                                "display_id_1" /* displayId */,
-                                100 /* sensorLocationX */,
-                                300 /* sensorLocationY */,
-                                20 /* sensorRadius */
-                            )
-                    )
-            )
-
-            val displayId by collectLastValue(interactor.displayId)
-            val offsets by collectLastValue(interactor.overlayOffsets)
-
-            // Assert offsets of empty displayId.
-            assertThat(displayId).isEqualTo("")
-            assertThat(offsets?.displayId).isEqualTo("")
-            assertThat(offsets?.sensorLocationX).isEqualTo(540)
-            assertThat(offsets?.sensorLocationY).isEqualTo(1636)
-            assertThat(offsets?.sensorRadius).isEqualTo(130)
-
-            // Offsets should be updated correctly.
-            interactor.onDisplayChanged("display_id_1")
-            assertThat(displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.sensorLocationX).isEqualTo(100)
-            assertThat(offsets?.sensorLocationY).isEqualTo(300)
-            assertThat(offsets?.sensorRadius).isEqualTo(20)
-
-            // Should return default offset when the displayId is invalid.
-            interactor.onDisplayChanged("invalid_display_id")
-            assertThat(displayId).isEqualTo("invalid_display_id")
-            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
-            assertThat(offsets?.sensorLocationX)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
-            assertThat(offsets?.sensorLocationY)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
-            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
new file mode 100644
index 0000000..99501c42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
+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.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.REST_TO_UNLOCK
+import com.android.systemui.res.R
+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.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsSensorInteractorTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private lateinit var testScope: TestScope
+
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
+
+    private lateinit var underTest: SideFpsSensorInteractor
+
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var displayStateInteractor: DisplayStateInteractor
+
+    private val contextDisplayInfo = DisplayInfo()
+    private val displayChangeEvent = MutableStateFlow(0)
+    private val currentRotation = MutableStateFlow(ROTATION_0)
+
+    @Before
+    fun setup() {
+        testScope = TestScope(StandardTestDispatcher())
+        mContext = spy(mContext)
+
+        val displayManager = mock(DisplayManagerGlobal::class.java)
+        val resources = mContext.resources
+        whenever(mContext.display)
+            .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources))
+        whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent)
+        whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation)
+
+        contextDisplayInfo.uniqueId = "current-display"
+
+        underTest =
+            SideFpsSensorInteractor(
+                mContext,
+                fingerprintRepository,
+                windowManager,
+                displayStateInteractor,
+                FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) }
+            )
+    }
+
+    @Test
+    fun testSfpsSensorAvailable() =
+        testScope.runTest {
+            val isAvailable by collectLastValue(underTest.isAvailable)
+
+            setupFingerprint(FingerprintSensorType.POWER_BUTTON)
+            assertThat(isAvailable).isTrue()
+
+            setupFingerprint(FingerprintSensorType.HOME_BUTTON)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.REAR)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UNKNOWN)
+            assertThat(isAvailable).isFalse()
+        }
+
+    @Test
+    fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() =
+        testScope.runTest {
+            assertThat(collectLastValue(underTest.authenticationDuration)())
+                .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration))
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+        testScope.runTest {
+            /*
+            (0,0)                (1000,0)
+               ------------------
+              |  ^^^^^           |  (1000, 200)
+              |   status bar    || <--- start of sensor at Rotation_0
+              |                 || <--- end of sensor
+              |                  |  (1000, 300)
+              |                  |
+               ------------------ (1000, 800)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_0,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(1000)
+            assertThat(sensorLocation!!.top).isEqualTo(200)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+        testScope.runTest {
+            /*
+            (800,0)                     (800, 1000)
+                  ---------------------
+                 |                     | (600, 1000)
+                 |   <                || <--- end of sensor at Rotation_270
+                 |   < status bar     || <--- start of sensor
+                 |   <                 | (500, 1000)
+                 |   <                 |
+            (0,0) ---------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_270,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(500)
+            assertThat(sensorLocation!!.top).isEqualTo(1000)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+        testScope.runTest {
+            /*
+                                            (0,0)
+                       ---------------------
+                      |                     | (200, 0)
+                      |               >    || <--- end of sensor at Rotation_270
+                      |    status bar >    || <--- start of sensor
+                      |               >     | (300, 0)
+                      |               >     |
+            (800,1000) ---------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_90,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(200)
+            assertThat(sensorLocation!!.top).isEqualTo(0)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+        testScope.runTest {
+            /*
+
+            (1000,800) ---------------------
+                      |                     | (0, 600)
+                      |                    || <--- end of sensor at Rotation_270
+                      |    status bar      || <--- start of sensor
+                      |   \/\/\/\/\/\/\/    | (0, 500)
+                      |                     |
+                       ---------------------  (0,0)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_180,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(0)
+            assertThat(sensorLocation!!.top).isEqualTo(500)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+        testScope.runTest {
+            /*
+            (0,0)   (500,0)   (600,0)   (1000,0)
+               ____________===_________
+              |                        |
+              |  ^^^^^                 |
+              |   status bar           |
+              |                        |
+               ------------------------ (1000, 800)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_0,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(500)
+            assertThat(sensorLocation!!.top).isEqualTo(0)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+        testScope.runTest {
+            /*
+                  (0,1000)   (0,500)   (0,400)   (0,0)
+                        ____________===_________
+                       |                        |
+                       |               >        |
+                       |   status bar  >        |
+                       |               >        |
+            (800, 1000) ------------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_90,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(0)
+            assertThat(sensorLocation!!.top).isEqualTo(400)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+        testScope.runTest {
+            /*
+            (1000, 800)  (500, 800)   (400, 800)   (0,800)
+                       ____________===_________
+                      |                        |
+                      |                        |
+                      |   status bar           |
+                      |  \/ \/ \/ \/ \/ \/ \/  |
+                       ------------------------ (0,0)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_180,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(400)
+            assertThat(sensorLocation!!.top).isEqualTo(800)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+        testScope.runTest {
+            /*
+                        (800, 500)  (800, 600)
+            (800, 0) ____________===_________ (800,1000)
+                    |  <                     |
+                    |  <                     |
+                    |  < status bar          |
+                    |  <                     |
+               (0,0) ------------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_270,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(800)
+            assertThat(sensorLocation!!.top).isEqualTo(500)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    private suspend fun TestScope.setupFPLocationAndDisplaySize(
+        width: Int,
+        height: Int,
+        sensorLocationX: Int = 0,
+        sensorLocationY: Int = 0,
+        rotation: DisplayRotation,
+        sensorWidth: Int
+    ) {
+        overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
+        setupDisplayDimensions(width, height)
+        currentRotation.value = rotation
+        setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+    }
+
+    private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
+        whenever(windowManager.maximumWindowMetrics)
+            .thenReturn(
+                WindowMetrics(
+                    Rect(0, 0, displayWidth, displayHeight),
+                    mock(WindowInsets::class.java)
+                )
+            )
+    }
+
+    private suspend fun TestScope.setupFingerprint(
+        fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
+        x: Int = 0,
+        y: Int = 0,
+        displayId: String = "display_id_1"
+    ) {
+        contextDisplayInfo.uniqueId = displayId
+        fingerprintRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations =
+                mapOf(
+                    "someOtherDisplayId" to
+                        SensorLocationInternal(
+                            "someOtherDisplayId",
+                            x + 100,
+                            y + 100,
+                            0,
+                        ),
+                    displayId to
+                        SensorLocationInternal(
+                            displayId,
+                            x,
+                            y,
+                            0,
+                        )
+                )
+        )
+        // Emit a display change event, this happens whenever any display related change happens,
+        // rotation, active display changing etc, display switched off/on.
+        displayChangeEvent.emit(1)
+
+        runCurrent()
+    }
+}
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 6b9c34b..bbf471c 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
@@ -24,6 +24,7 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -55,6 +56,7 @@
 
     @Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
     @Mock private lateinit var overlayBounds: Rect
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
 
     private lateinit var underTest: UdfpsOverlayInteractor
 
@@ -104,7 +106,12 @@
         }
 
     private fun createUdpfsOverlayInteractor() {
-        underTest = UdfpsOverlayInteractor(authController, testScope.backgroundScope)
+        underTest =
+            UdfpsOverlayInteractor(
+                authController,
+                selectedUserInteractor,
+                testScope.backgroundScope
+            )
         testScope.runCurrent()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.content.res.Configuration
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-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.display.data.repository.FakeDisplayRepository
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-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.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class PromptFingerprintIconViewModelTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
-    private lateinit var displayRepository: FakeDisplayRepository
-    private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
-    private lateinit var promptRepository: FakePromptRepository
-    private lateinit var displayStateRepository: FakeDisplayStateRepository
-
-    private val testScope = TestScope(StandardTestDispatcher())
-    private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
-    private lateinit var promptSelectorInteractor: PromptSelectorInteractor
-    private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var viewModel: PromptFingerprintIconViewModel
-
-    @Before
-    fun setup() {
-        displayRepository = FakeDisplayRepository()
-        fingerprintRepository = FakeFingerprintPropertyRepository()
-        promptRepository = FakePromptRepository()
-        displayStateRepository = FakeDisplayStateRepository()
-
-        promptSelectorInteractor =
-            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
-        displayStateInteractor =
-            DisplayStateInteractorImpl(
-                testScope.backgroundScope,
-                mContext,
-                fakeExecutor,
-                displayStateRepository,
-                displayRepository,
-            )
-        viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-    }
-
-    @Test
-    fun sfpsIconUpdates_onConfigurationChanged() {
-        testScope.runTest {
-            runCurrent()
-            configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON)
-            val testConfig = Configuration()
-            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
-            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
-            val currentIcon = collectLastValue(viewModel.iconAsset)
-
-            testConfig.smallestScreenWidthDp = folded
-            viewModel.onConfigurationChanged(testConfig)
-            val foldedIcon = currentIcon()
-
-            testConfig.smallestScreenWidthDp = unfolded
-            viewModel.onConfigurationChanged(testConfig)
-            val unfoldedIcon = currentIcon()
-
-            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
-        }
-    }
-
-    private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) {
-        fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf())
-    }
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
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 ca6df40..b695a0e 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,8 +16,10 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.content.res.Configuration
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
@@ -36,12 +38,15 @@
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -66,6 +71,7 @@
 
 private const val USER_ID = 4
 private const val CHALLENGE = 2L
+private const val DELAY = 1000L
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -88,11 +94,22 @@
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
+    private lateinit var iconViewModel: PromptIconViewModel
     private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
         fingerprintRepository = FakeFingerprintPropertyRepository()
+        testCase.fingerprint?.let {
+            fingerprintRepository.setProperties(
+                it.sensorId,
+                it.sensorStrength.toSensorStrength(),
+                it.sensorType.toSensorType(),
+                it.allLocations.associateBy { sensorLocationInternal ->
+                    sensorLocationInternal.displayId
+                }
+            )
+        }
         promptRepository = FakePromptRepository()
         displayStateRepository = FakeDisplayStateRepository()
         displayRepository = FakeDisplayRepository()
@@ -110,6 +127,7 @@
 
         viewModel =
             PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+        iconViewModel = viewModel.iconViewModel
         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
@@ -123,7 +141,6 @@
             val modalities by collectLastValue(viewModel.modalities)
             val message by collectLastValue(viewModel.message)
             val size by collectLastValue(viewModel.size)
-            val legacyState by collectLastValue(viewModel.legacyState)
 
             assertThat(authenticating).isFalse()
             assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -133,7 +150,6 @@
             }
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(size).isEqualTo(expectedSize)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
 
             val startMessage = "here we go"
             viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -143,7 +159,6 @@
             assertThat(authenticated?.isNotAuthenticated).isTrue()
             assertThat(size).isEqualTo(expectedSize)
             assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         }
 
     @Test
@@ -205,6 +220,472 @@
         assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
     }
 
+    @Test
+    fun start_idle_and_show_authenticating_iconUpdate() =
+        runGenericTest(doNotStart = true) {
+            val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+            if (forceExplicitFlow) {
+                viewModel.ensureFingerprintHasStarted(isDelayed = true)
+            }
+
+            val startMessage = "here we go"
+            viewModel.showAuthenticating(startMessage, isRetry = false)
+
+            if (testCase.isFingerprintOnly) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    val expectedOverlayAsset =
+                        when (currentRotation) {
+                            DisplayRotation.ROTATION_0 ->
+                                R.raw.biometricprompt_fingerprint_to_error_landscape
+                            DisplayRotation.ROTATION_90 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                            DisplayRotation.ROTATION_180 ->
+                                R.raw.biometricprompt_fingerprint_to_error_landscape
+                            DisplayRotation.ROTATION_270 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                            else -> throw Exception("invalid rotation")
+                        }
+                    assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                } else {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(false)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                val expectedIconAsset =
+                    if (shouldPulseAnimation!!) {
+                        if (lastPulseLightToDark!!) {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        } else {
+                            R.drawable.face_dialog_pulse_light_to_dark
+                        }
+                    } else {
+                        R.drawable.face_dialog_pulse_dark_to_light
+                    }
+                assertThat(iconAsset).isEqualTo(expectedIconAsset)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(true)
+            }
+
+            if (testCase.isCoex) {
+                if (testCase.confirmationRequested || forceExplicitFlow) {
+                    // explicit flow
+                    val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                    val shouldAnimateIconOverlay by
+                        collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                    // TODO: Update when SFPS co-ex is implemented
+                    if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                        assertThat(iconOverlayAsset).isEqualTo(-1)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(false)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                    }
+                } else {
+                    // implicit flow
+                    val shouldRepeatAnimation by
+                        collectLastValue(iconViewModel.shouldRepeatAnimation)
+                    val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                    val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                    val expectedIconAsset =
+                        if (shouldPulseAnimation!!) {
+                            if (lastPulseLightToDark!!) {
+                                R.drawable.face_dialog_pulse_dark_to_light
+                            } else {
+                                R.drawable.face_dialog_pulse_light_to_dark
+                            }
+                        } else {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        }
+                    assertThat(iconAsset).isEqualTo(expectedIconAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldRepeatAnimation).isEqualTo(true)
+                }
+            }
+        }
+
+    @Test
+    fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest {
+        val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+        val iconAsset by collectLastValue(iconViewModel.iconAsset)
+        val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+        val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+        val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+        if (forceExplicitFlow) {
+            viewModel.ensureFingerprintHasStarted(isDelayed = true)
+        }
+
+        val errorJob = launch {
+            viewModel.showTemporaryError(
+                "so sad",
+                messageAfterError = "",
+                authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex,
+            )
+            // Usually done by binder
+            iconViewModel.setPreviousIconWasError(true)
+            iconViewModel.setPreviousIconOverlayWasError(true)
+        }
+
+        if (testCase.isFingerprintOnly) {
+            val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+            val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                val expectedOverlayAsset =
+                    when (currentRotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            R.raw.biometricprompt_fingerprint_to_error_landscape
+                        DisplayRotation.ROTATION_90 ->
+                            R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                        DisplayRotation.ROTATION_180 ->
+                            R.raw.biometricprompt_fingerprint_to_error_landscape
+                        DisplayRotation.ROTATION_270 ->
+                            R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                        else -> throw Exception("invalid rotation")
+                    }
+                assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+            } else {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+
+            // Clear error, restart authenticating
+            errorJob.join()
+
+            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                val expectedOverlayAsset =
+                    when (currentRotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                        DisplayRotation.ROTATION_90 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+                        DisplayRotation.ROTATION_180 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                        DisplayRotation.ROTATION_270 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+                        else -> throw Exception("invalid rotation")
+                    }
+                assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+            } else {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+        }
+
+        if (testCase.isFaceOnly) {
+            val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+            val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+            assertThat(shouldPulseAnimation!!).isEqualTo(false)
+            assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
+            assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
+            assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
+
+            // Clear error, go to idle
+            errorJob.join()
+
+            assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle)
+            assertThat(iconContentDescriptionId)
+                .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
+            assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
+        }
+
+        if (testCase.isCoex) {
+            val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+            val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+            // TODO: Update when SFPS co-ex is implemented
+            if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+
+            // Clear error, restart authenticating
+            errorJob.join()
+
+            if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest {
+        if (!testCase.confirmationRequested) {
+            val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            if (testCase.isFingerprintOnly) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    val expectedOverlayAsset =
+                        when (currentRotation) {
+                            DisplayRotation.ROTATION_0 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            DisplayRotation.ROTATION_90 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+                            DisplayRotation.ROTATION_180 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            DisplayRotation.ROTATION_270 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                            else -> throw Exception("invalid rotation")
+                        }
+                    assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+                } else {
+                    val isAuthenticated by collectLastValue(viewModel.isAuthenticated)
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+
+            // If co-ex, using implicit flow (explicit flow always requires confirmation)
+            if (testCase.isFaceOnly || testCase.isCoex) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
+    fun shows_pending_confirmation_iconUpdate() = runGenericTest {
+        if (
+            (testCase.isFaceOnly || testCase.isCoex) &&
+                testCase.authenticatedByFace &&
+                testCase.confirmationRequested
+        ) {
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+
+            // explicit flow because confirmation requested
+            if (testCase.isCoex) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                // TODO: Update when SFPS co-ex is implemented
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
+        if (
+            (testCase.isFaceOnly || testCase.isCoex) &&
+                testCase.authenticatedByFace &&
+                testCase.confirmationRequested
+        ) {
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            viewModel.confirmAuthenticated()
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+
+            // explicit flow because confirmation requested
+            if (testCase.isCoex) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                // TODO: Update when SFPS co-ex is implemented
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val testConfig = Configuration()
+            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            testConfig.smallestScreenWidthDp = folded
+            iconViewModel.onConfigurationChanged(testConfig)
+            val foldedIcon = currentIcon
+
+            testConfig.smallestScreenWidthDp = unfolded
+            iconViewModel.onConfigurationChanged(testConfig)
+            val unfoldedIcon = currentIcon
+
+            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onRotation() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+            val iconRotation0 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+            val iconRotation90 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+            val iconRotation180 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+            val iconRotation270 = currentIcon
+
+            assertThat(iconRotation0).isEqualTo(iconRotation180)
+            assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+            assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            displayStateRepository.setIsInRearDisplayMode(false)
+            val iconNotRearDisplayMode = currentIcon
+
+            displayStateRepository.setIsInRearDisplayMode(true)
+            val iconRearDisplayMode = currentIcon
+
+            assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+        }
+    }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -213,7 +694,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
 
         val authWithSmallPrompt =
             testCase.shouldStartAsImplicitFlow &&
@@ -221,14 +701,12 @@
         assertThat(authenticating).isTrue()
         assertThat(authenticated?.isNotAuthenticated).isTrue()
         assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         assertButtonsVisible(negative = !authWithSmallPrompt)
 
-        val delay = 1000L
-        viewModel.showAuthenticated(authenticatedModality, delay)
+        viewModel.showAuthenticated(authenticatedModality, DELAY)
 
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(authenticated?.delay).isEqualTo(delay)
+        assertThat(authenticated?.delay).isEqualTo(DELAY)
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
         assertThat(size)
             .isEqualTo(
@@ -238,14 +716,7 @@
                     PromptSize.SMALL
                 }
             )
-        assertThat(legacyState)
-            .isEqualTo(
-                if (expectConfirmation) {
-                    BiometricState.STATE_PENDING_CONFIRMATION
-                } else {
-                    BiometricState.STATE_AUTHENTICATED
-                }
-            )
+
         assertButtonsVisible(
             cancel = expectConfirmation,
             confirm = expectConfirmation,
@@ -298,7 +769,6 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
 
         val errorJob = launch {
@@ -312,7 +782,6 @@
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
         assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
         assertThat(messageVisible).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
 
         // temporary error should disappear after a delay
         errorJob.join()
@@ -323,17 +792,6 @@
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(messageVisible).isFalse()
         }
-        val clearIconError = !restart
-        assertThat(legacyState)
-            .isEqualTo(
-                if (restart) {
-                    BiometricState.STATE_AUTHENTICATING
-                } else if (clearIconError) {
-                    BiometricState.STATE_IDLE
-                } else {
-                    BiometricState.STATE_HELP
-                }
-            )
 
         assertThat(authenticating).isEqualTo(restart)
         assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -488,7 +946,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -506,7 +963,6 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -524,7 +980,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticating).isFalse()
@@ -532,8 +987,6 @@
         assertThat(authenticated?.isAuthenticated).isTrue()
 
         if (testCase.isFaceOnly && expectConfirmation) {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-
             assertThat(size).isEqualTo(PromptSize.MEDIUM)
             assertButtonsVisible(
                 cancel = true,
@@ -543,8 +996,6 @@
             viewModel.confirmAuthenticated()
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertButtonsVisible()
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         }
     }
 
@@ -563,7 +1014,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -581,7 +1031,6 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -610,12 +1059,10 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
 
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
 
@@ -632,7 +1079,6 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
 
         if (testCase.isCoex && testCase.authenticatedByFingerprint) {
@@ -642,11 +1088,7 @@
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        if (confirmationRequired == true) {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
-        }
+
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
         assertThat(authenticating).isFalse()
@@ -785,6 +1227,15 @@
                     authenticatedModality = BiometricModality.Fingerprint,
                 ),
                 TestCase(
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                ),
+                TestCase(
                     face = faceSensorPropertiesInternal(strong = true).first(),
                     authenticatedModality = BiometricModality.Face,
                     confirmationRequested = true,
@@ -794,6 +1245,16 @@
                     authenticatedModality = BiometricModality.Fingerprint,
                     confirmationRequested = true,
                 ),
+                TestCase(
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                    confirmationRequested = true,
+                ),
             )
 
         private val coexTestCases =
@@ -834,7 +1295,9 @@
         val modality =
             when {
                 fingerprint != null && face != null -> "coex"
-                fingerprint != null -> "fingerprint only"
+                fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
+                fingerprint != null && !fingerprint.isAnySidefpsType ->
+                    "fingerprint only, non-sideFps"
                 face != null -> "face only"
                 else -> "?"
             }
@@ -864,6 +1327,8 @@
     val isCoex: Boolean
         get() = face != null && fingerprint != null
 
+    @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType
+
     val shouldStartAsImplicitFlow: Boolean
         get() = (isFaceOnly || isCoex) && !confirmationRequested
 }
@@ -890,3 +1355,5 @@
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
     )
 }
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cc4eca5..b48bc1d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.res.R.string.kg_trust_agent_disabled
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -86,6 +87,7 @@
     @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
     @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     private lateinit var testScope: TestScope
@@ -119,6 +121,7 @@
                 keyguardUpdateMonitor,
                 fakeTrustRepository,
                 testScope.backgroundScope,
+                mSelectedUserInteractor,
             )
         underTest =
             BouncerMessageInteractor(
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
index 9a5b4585..d6aa9ac 100644
--- 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
@@ -25,7 +25,6 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.DejankUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -37,7 +36,9 @@
 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.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
@@ -70,6 +71,7 @@
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     private lateinit var mainHandler: FakeHandler
     private lateinit var underTest: PrimaryBouncerInteractor
     private lateinit var resources: TestableResources
@@ -100,6 +102,7 @@
                 keyguardUpdateMonitor,
                 trustRepository,
                 testScope.backgroundScope,
+                mSelectedUserInteractor,
             )
         whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -108,6 +111,27 @@
     }
 
     @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)
@@ -399,7 +423,7 @@
         mainHandler.setMode(FakeHandler.Mode.QUEUEING)
 
         // GIVEN bouncer should be delayed due to face auth
-        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardStateController.isFaceEnrolled).thenReturn(true)
         whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
             .thenReturn(true)
         whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true)
@@ -425,7 +449,7 @@
 
         // GIVEN bouncer should not be delayed because device isn't in the right posture for
         // face auth
-        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardStateController.isFaceEnrolled).thenReturn(true)
         whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
             .thenReturn(true)
         whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index 2018e61..d1b120e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -28,8 +28,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.TestScope
@@ -51,8 +51,8 @@
     @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     private lateinit var underTest: PrimaryBouncerInteractor
 
@@ -74,6 +74,7 @@
                 keyguardUpdateMonitor,
                 Mockito.mock(TrustRepository::class.java),
                 TestScope().backgroundScope,
+                mSelectedUserInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 802f8e6..b75f3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -32,8 +32,8 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
@@ -60,7 +60,7 @@
     @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     val repository = FakeKeyguardBouncerRepository()
@@ -82,6 +82,7 @@
                 keyguardUpdateMonitor,
                 Mockito.mock(TrustRepository::class.java),
                 TestScope().backgroundScope,
+                mSelectedUserInteractor,
             )
         underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 037c1ba..6d3cc4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -28,11 +28,11 @@
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -46,8 +46,8 @@
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -74,7 +74,7 @@
     @Mock
     lateinit var contentResolver: ContentResolver
     @Mock
-    lateinit var userTracker: UserTracker
+    lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private lateinit var underTest: CameraGestureHelper
 
@@ -103,7 +103,7 @@
             cameraIntents = cameraIntents,
             contentResolver = contentResolver,
             uiExecutor = MoreExecutors.directExecutor(),
-            userTracker = userTracker,
+            selectedUserInteractor = mSelectedUserInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 442bf91..80fe9e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
@@ -75,6 +76,8 @@
     private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock
     private BatteryController mBatteryController;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private final DockManagerFake mDockManager = new DockManagerFake();
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -90,7 +93,7 @@
                 mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
                 mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
                 mBatteryController,
-                mDockManager, mFakeExecutor, mFakeSystemClock);
+                mDockManager, mFakeExecutor, mFakeSystemClock, () -> mSelectedUserInteractor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 15e5e1c..882bcab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -17,7 +17,6 @@
 package com.android.systemui.colorextraction;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -37,6 +36,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,6 +63,8 @@
     private WallpaperManager mWallpaperManager;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private ColorExtractor.GradientColors mColors;
     private SysuiColorExtractor mColorExtractor;
 
@@ -83,7 +85,8 @@
                 mock(ConfigurationController.class),
                 mWallpaperManager,
                 mDumpManager,
-                true /* immediately */);
+                true /* immediately */,
+                () -> mSelectedUserInteractor);
     }
 
     @Test
@@ -101,21 +104,6 @@
     }
 
     @Test
-    public void getColors_fallbackWhenMediaIsVisible() {
-        simulateEvent(mColorExtractor);
-        mColorExtractor.setHasMediaArtwork(true);
-
-        ColorExtractor.GradientColors fallbackColors = mColorExtractor.getNeutralColors();
-
-        for (int type : sTypes) {
-            assertEquals("Not using fallback!",
-                    mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, type), fallbackColors);
-            assertNotEquals("Media visibility should not affect system wallpaper.",
-                    mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, type), fallbackColors);
-        }
-    }
-
-    @Test
     public void onUiModeChanged_reloadsColors() {
         Tonal tonal = mock(Tonal.class);
         ConfigurationController configurationController = mock(ConfigurationController.class);
@@ -125,7 +113,8 @@
                 configurationController,
                 mWallpaperManager,
                 mDumpManager,
-                true /* immediately */);
+                true /* immediately */,
+                () -> mSelectedUserInteractor);
         verify(configurationController).addCallback(eq(sysuiColorExtractor));
 
         reset(tonal);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
new file mode 100644
index 0000000..034b802
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.common.ui
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConfigurationStateTest : SysuiTestCase() {
+
+    private val configurationController: ConfigurationController = mock()
+    private val layoutInflater = TestLayoutInflater()
+
+    val underTest = ConfigurationState(configurationController, context, layoutInflater)
+
+    @Test
+    fun reinflateAndBindLatest_inflatesWithoutEmission() = runTest {
+        var callbackCount = 0
+        backgroundScope.launch {
+            underTest.reinflateAndBindLatest<View>(
+                resource = 0,
+                root = null,
+                attachToRoot = false,
+            ) {
+                callbackCount++
+                null
+            }
+        }
+
+        // Inflates without an emission
+        runCurrent()
+        assertThat(layoutInflater.inflationCount).isEqualTo(1)
+        assertThat(callbackCount).isEqualTo(1)
+    }
+
+    @Test
+    fun reinflateAndBindLatest_reinflatesOnThemeChanged() = runTest {
+        var callbackCount = 0
+        backgroundScope.launch {
+            underTest.reinflateAndBindLatest<View>(
+                resource = 0,
+                root = null,
+                attachToRoot = false,
+            ) {
+                callbackCount++
+                null
+            }
+        }
+        runCurrent()
+
+        val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
+            verify(configurationController, atLeastOnce()).addCallback(capture())
+        }
+
+        listOf(1, 2, 3).forEach { count ->
+            assertThat(layoutInflater.inflationCount).isEqualTo(count)
+            assertThat(callbackCount).isEqualTo(count)
+            configListeners.forEach { it.onThemeChanged() }
+            runCurrent()
+        }
+    }
+
+    @Test
+    fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() = runTest {
+        var callbackCount = 0
+        backgroundScope.launch {
+            underTest.reinflateAndBindLatest<View>(
+                resource = 0,
+                root = null,
+                attachToRoot = false,
+            ) {
+                callbackCount++
+                null
+            }
+        }
+        runCurrent()
+
+        val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
+            verify(configurationController, atLeastOnce()).addCallback(capture())
+        }
+
+        listOf(1, 2, 3).forEach { count ->
+            assertThat(layoutInflater.inflationCount).isEqualTo(count)
+            assertThat(callbackCount).isEqualTo(count)
+            configListeners.forEach { it.onDensityOrFontScaleChanged() }
+            runCurrent()
+        }
+    }
+
+    @Test
+    fun testReinflateAndBindLatest_disposesOnCancel() = runTest {
+        var callbackCount = 0
+        var disposed = false
+        val job = launch {
+            underTest.reinflateAndBindLatest<View>(
+                resource = 0,
+                root = null,
+                attachToRoot = false,
+            ) {
+                callbackCount++
+                DisposableHandle { disposed = true }
+            }
+        }
+
+        runCurrent()
+        job.cancelAndJoin()
+        assertThat(disposed).isTrue()
+    }
+
+    inner class TestLayoutInflater : LayoutInflater(context) {
+
+        var inflationCount = 0
+
+        override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View {
+            inflationCount++
+            return View(context)
+        }
+
+        override fun cloneInContext(p0: Context?): LayoutInflater {
+            // not needed for this test
+            return this
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
index 1b2fc93d..2f4fc96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
@@ -67,6 +67,7 @@
                 isAttachedToWindow = { isAttachedToWindow },
                 onLongPressDetected = onLongPressDetected,
                 onSingleTapDetected = onSingleTapDetected,
+                longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }
             )
         underTest.isLongPressHandlingEnabled = true
     }
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
new file mode 100644
index 0000000..30a5497
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.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/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 3df9cbb..fcb191b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -11,11 +11,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -35,6 +39,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -55,30 +60,52 @@
 
     @Mock private lateinit var userTracker: UserTracker
 
-    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var featureFlags: FeatureFlagsClassic
 
     @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
 
+    @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+
+    @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+
+    @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+
+    private lateinit var communalRepository: FakeCommunalRepository
+
     private lateinit var logBuffer: LogBuffer
 
     private val testDispatcher = StandardTestDispatcher()
+
     private val testScope = TestScope(testDispatcher)
 
+    private val fakeAllowlist =
+        listOf(
+            "com.android.fake/WidgetProviderA",
+            "com.android.fake/WidgetProviderB",
+            "com.android.fake/WidgetProviderC",
+        )
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         logBuffer = FakeLogBuffer.Factory.create()
+        communalRepository = FakeCommunalRepository()
 
-        featureFlagEnabled(true)
+        communalEnabled(true)
+        widgetOnKeyguardEnabled(true)
+        setAppWidgetIds(emptyList())
+
+        overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
+
         whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
         whenever(userTracker.userHandle).thenReturn(userHandle)
     }
 
     @Test
-    fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+    fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
         testScope.runTest {
-            featureFlagEnabled(false)
+            communalEnabled(false)
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
             verifyBroadcastReceiverNeverRegistered()
@@ -115,7 +142,7 @@
             job.cancel()
             runCurrent()
 
-            Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+            verify(broadcastDispatcher).unregisterReceiver(receiver)
         }
 
     @Test
@@ -152,7 +179,7 @@
             installedProviders(listOf(stopwatchProviderInfo))
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost).allocateAppWidgetId()
         }
 
     @Test
@@ -171,8 +198,8 @@
 
             // Verify app widget id allocated
             assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
-            Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
+            verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
 
             // User locked again
             userUnlocked(false)
@@ -180,7 +207,7 @@
 
             // Verify app widget id deleted
             assertThat(lastStopwatchProviderInfo()).isNull()
-            Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+            verify(appWidgetHost).deleteAppWidgetId(123456)
         }
 
     @Test
@@ -189,13 +216,13 @@
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost, Mockito.never()).startListening()
+            verify(appWidgetHost, Mockito.never()).startListening()
 
             userUnlocked(true)
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).startListening()
+            verify(appWidgetHost).startListening()
         }
 
     @Test
@@ -209,21 +236,105 @@
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).startListening()
-            Mockito.verify(appWidgetHost, Mockito.never()).stopListening()
+            verify(appWidgetHost).startListening()
+            verify(appWidgetHost, Mockito.never()).stopListening()
 
             userUnlocked(false)
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).stopListening()
+            verify(appWidgetHost).stopListening()
+        }
+
+    @Test
+    fun getCommunalWidgetAllowList_onInit() {
+        testScope.runTest {
+            val repository = initCommunalWidgetRepository()
+            val communalWidgetAllowlist = repository.communalWidgetAllowlist
+            assertThat(
+                    listOf(
+                        CommunalWidgetMetadata(
+                            componentName = fakeAllowlist[0],
+                            priority = 3,
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
+                        CommunalWidgetMetadata(
+                            componentName = fakeAllowlist[1],
+                            priority = 2,
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
+                        CommunalWidgetMetadata(
+                            componentName = fakeAllowlist[2],
+                            priority = 1,
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
+                    )
+                )
+                .containsExactly(*communalWidgetAllowlist.toTypedArray())
+        }
+    }
+
+    // This behavior is temporary before the local database is set up.
+    @Test
+    fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
+        testScope.runTest {
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
+            setAppWidgetIds(listOf(1, 2, 3))
+            whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+            userUnlocked(true)
+
+            val repository = initCommunalWidgetRepository()
+
+            collectLastValue(repository.communalWidgets)()
+
+            verify(appWidgetHost).deleteAppWidgetId(1)
+            verify(appWidgetHost).deleteAppWidgetId(2)
+            verify(appWidgetHost).deleteAppWidgetId(3)
+        }
+
+    @Test
+    fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
+        testScope.runTest {
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
+            userUnlocked(true)
+
+            whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
+
+            val repository = initCommunalWidgetRepository()
+
+            val inventory by collectLastValue(repository.communalWidgets)
+
+            assertThat(
+                    listOf(
+                        CommunalWidgetContentModel(
+                            appWidgetId = 0,
+                            providerInfo = providerInfoA,
+                            priority = 3,
+                        ),
+                        CommunalWidgetContentModel(
+                            appWidgetId = 1,
+                            providerInfo = providerInfoB,
+                            priority = 2,
+                        ),
+                        CommunalWidgetContentModel(
+                            appWidgetId = 2,
+                            providerInfo = providerInfoC,
+                            priority = 1,
+                        ),
+                    )
+                )
+                .containsExactly(*inventory!!.toTypedArray())
         }
 
     private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
         return CommunalWidgetRepositoryImpl(
+            context,
             appWidgetManager,
             appWidgetHost,
             broadcastDispatcher,
+            communalRepository,
             packageManager,
             userManager,
             userTracker,
@@ -233,7 +344,7 @@
     }
 
     private fun verifyBroadcastReceiverRegistered() {
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 any(),
                 any(),
@@ -245,7 +356,7 @@
     }
 
     private fun verifyBroadcastReceiverNeverRegistered() {
-        Mockito.verify(broadcastDispatcher, Mockito.never())
+        verify(broadcastDispatcher, Mockito.never())
             .registerReceiver(
                 any(),
                 any(),
@@ -258,7 +369,7 @@
 
     private fun broadcastReceiverUpdate(): BroadcastReceiver {
         val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 broadcastReceiverCaptor.capture(),
                 any(),
@@ -271,7 +382,11 @@
         return broadcastReceiverCaptor.value
     }
 
-    private fun featureFlagEnabled(enabled: Boolean) {
+    private fun communalEnabled(enabled: Boolean) {
+        communalRepository.setIsCommunalEnabled(enabled)
+    }
+
+    private fun widgetOnKeyguardEnabled(enabled: Boolean) {
         whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
     }
 
@@ -282,4 +397,8 @@
     private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
         whenever(appWidgetManager.installedProviders).thenReturn(providers)
     }
+
+    private fun setAppWidgetIds(ids: List<Int>) {
+        whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cdc42e0..8e21f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -22,7 +22,7 @@
 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.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
new file mode 100644
index 0000000..0a9a15e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -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.systemui.communal.domain.interactor
+
+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
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.settings.UserTracker
+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.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.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CommunalTutorialInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var userTracker: UserTracker
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private lateinit var underTest: CommunalTutorialInteractor
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        keyguardInteractor = withDeps.keyguardInteractor
+        keyguardRepository = withDeps.repository
+        communalTutorialRepository = FakeCommunalTutorialRepository()
+
+        underTest =
+            CommunalTutorialInteractor(
+                keyguardInteractor = keyguardInteractor,
+                communalTutorialRepository = communalTutorialRepository,
+            )
+
+        whenever(userTracker.userHandle).thenReturn(mock())
+    }
+
+    @Test
+    fun tutorialUnavailable_whenKeyguardNotVisible() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            keyguardRepository.setKeyguardShowing(false)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
+    fun tutorialUnavailable_whenTutorialIsCompleted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
+    fun tutorialAvailable_whenTutorialNotStarted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            assertThat(isTutorialAvailable).isTrue()
+        }
+
+    @Test
+    fun tutorialAvailable_whenTutorialIsStarted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+            assertThat(isTutorialAvailable).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
index 41a8be9..33a6667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
@@ -6,6 +6,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection
 import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
 import org.junit.Before
 import org.junit.Test
@@ -18,6 +19,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class DefaultCommunalBlueprintTest : SysuiTestCase() {
+    @Mock private lateinit var hubSection: DefaultCommunalHubSection
     @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection
 
     private lateinit var blueprint: DefaultCommunalBlueprint
@@ -25,13 +27,14 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        blueprint = DefaultCommunalBlueprint(widgetSection)
+        blueprint = DefaultCommunalBlueprint(hubSection, widgetSection)
     }
 
     @Test
     fun addView() {
         val constraintLayout = ConstraintLayout(context, null)
         blueprint.replaceViews(null, constraintLayout)
+        verify(hubSection).addViews(constraintLayout)
         verify(widgetSection).addViews(constraintLayout)
     }
 
@@ -39,6 +42,7 @@
     fun applyConstraints() {
         val cs = ConstraintSet()
         blueprint.applyConstraints(cs)
+        verify(hubSection).applyConstraints(cs)
         verify(widgetSection).applyConstraints(cs)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
index 68ea7eb..6c2e136 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.demomode.DemoMode.ACTION_DEMO
 import com.android.systemui.demomode.DemoMode.COMMAND_STATUS
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
@@ -48,7 +48,7 @@
 
     @Mock private lateinit var dumpManager: DumpManager
 
-    private val globalSettings = FakeSettings()
+    private val globalSettings = FakeGlobalSettings()
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
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
new file mode 100644
index 0000000..9b8e581
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+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.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
+
+    private lateinit var repository: DeviceEntryHapticsRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var keyEventRepository: FakeKeyEventRepository
+    private lateinit var powerRepository: FakePowerRepository
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var underTest: DeviceEntryHapticsInteractor
+
+    @Before
+    fun setUp() {
+        repository = DeviceEntryHapticsRepositoryImpl()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        keyEventRepository = FakeKeyEventRepository()
+        powerRepository = FakePowerRepository()
+        systemClock = FakeSystemClock()
+        underTest =
+            DeviceEntryHapticsInteractor(
+                repository = repository,
+                fingerprintPropertyRepository = fingerprintPropertyRepository,
+                biometricSettingsRepository = biometricSettingsRepository,
+                keyEventInteractor = KeyEventInteractor(keyEventRepository),
+                powerInteractor =
+                    PowerInteractor(
+                        powerRepository,
+                        mock(FalsingCollector::class.java),
+                        mock(ScreenOffAnimationController::class.java),
+                        mock(StatusBarStateController::class.java),
+                    ),
+                systemClock = systemClock,
+                logger = mock(BiometricUnlockLogger::class.java),
+            )
+    }
+
+    @Test
+    fun nonPowerButtonFPS_vibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_vibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(false)
+
+        // It's been 10 seconds since the last power button wakeup
+        setAwakeFromPowerButton()
+        runCurrent()
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+
+        // It's been 10 seconds since the last power button wakeup
+        setAwakeFromPowerButton()
+        runCurrent()
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isFalse()
+    }
+
+    @Test
+    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(false)
+
+        // It's only been 50ms since the last power button wakeup
+        setAwakeFromPowerButton()
+        runCurrent()
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isFalse()
+    }
+
+    @Test
+    fun nonPowerButtonFPS_vibrateError() = runTest {
+        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+        underTest.vibrateError()
+        assertThat(playErrorHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_vibrateError() = runTest {
+        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        underTest.vibrateError()
+        assertThat(playErrorHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
+        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(true)
+        underTest.vibrateError()
+        assertThat(playErrorHaptic).isFalse()
+    }
+
+    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
+        fingerprintPropertyRepository.setProperties(
+            sensorId = 0,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations = mapOf(),
+        )
+    }
+
+    private fun setPowerButtonFingerprintProperty() {
+        setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
+    }
+
+    private fun setFingerprintEnrolled() {
+        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+    }
+
+    private fun setAwakeFromPowerButton() {
+        powerRepository.updateWakefulness(
+            WakefulnessState.AWAKE,
+            WakeSleepReason.POWER_BUTTON,
+            WakeSleepReason.POWER_BUTTON,
+            powerButtonLaunchGestureTriggered = false,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 7e1edd2..ba578a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.wakelock.WakeLockFake;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -85,6 +86,8 @@
     private DozeLog mDozeLog;
     @Mock
     private DozeScreenBrightness mDozeScreenBrightness;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     @Before
     public void setUp() throws Exception {
@@ -100,7 +103,7 @@
         mWakeLock = new WakeLockFake();
         mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
                 mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
-                mDozeScreenBrightness);
+                mDozeScreenBrightness, mSelectedUserInteractor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index a88a8e5..3cc0451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -52,9 +52,9 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeSensors.TriggerSensor;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.settings.FakeSettings;
@@ -102,7 +102,7 @@
     @Mock
     private DevicePostureController mDevicePostureController;
     @Mock
-    private UserTracker mUserTracker;
+    private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private ProximitySensor mProximitySensor;
 
@@ -122,7 +122,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
-        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+        when(mSelectedUserInteractor.getSelectedUserId())
+                .thenReturn(ActivityManager.getCurrentUser());
         when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
                 .thenReturn(new String[]{"tapSensor"});
         when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
@@ -435,7 +436,7 @@
         DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mSelectedUserInteractor);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             assertFalse(sensor.mIgnoresSetting);
@@ -480,7 +481,7 @@
         DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mSelectedUserInteractor);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             // THEN lift to wake's TriggerSensor enabledBySettings is false
@@ -499,7 +500,7 @@
         DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mSelectedUserInteractor);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             // THEN lift to wake's TriggerSensor enabledBySettings is true
@@ -514,7 +515,7 @@
             super(mResources, mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
-                    mDevicePostureController, mUserTracker);
+                    mDevicePostureController, mSelectedUserInteractor);
             for (TriggerSensor sensor : mTriggerSensors) {
                 if (sensor instanceof PluginSensor
                         && ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 494e230..3a6b075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -101,6 +102,8 @@
     @Mock
     private UserTracker mUserTracker;
     @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
     private SessionTracker mSessionTracker;
 
     private DozeTriggers mTriggers;
@@ -134,7 +137,7 @@
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
                 mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
                 mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mUserTracker, mSelectedUserInteractor);
         mTriggers.setDozeMachine(mMachine);
         waitForSensorManager();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
index 0e14591..52c6e22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -18,9 +18,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -61,80 +63,70 @@
     @Test
     fun restart_ImmediatelySatisfied() =
         testScope.runTest {
-            conditionA.canRestart = true
-            conditionB.canRestart = true
+            conditionA.canRestart.emit(true)
+            conditionB.canRestart.emit(true)
             restarter.restartSystemUI("Restart for test")
-            advanceUntilIdle()
+            runCurrent()
             verify(systemExitRestarter).restartSystemUI(any())
         }
 
     @Test
     fun restart_WaitsForConditionA() =
         testScope.runTest {
-            conditionA.canRestart = false
-            conditionB.canRestart = true
+            conditionA.canRestart.emit(false)
+            conditionB.canRestart.emit(true)
 
             restarter.restartSystemUI("Restart for test")
-            advanceUntilIdle()
+            runCurrent()
             // No restart occurs yet.
             verify(systemExitRestarter, never()).restartSystemUI(any())
 
-            conditionA.canRestart = true
-            conditionA.retryFn?.invoke()
-            advanceUntilIdle()
+            conditionA.canRestart.emit(true)
+            runCurrent()
             verify(systemExitRestarter).restartSystemUI(any())
         }
 
     @Test
     fun restart_WaitsForConditionB() =
         testScope.runTest {
-            conditionA.canRestart = true
-            conditionB.canRestart = false
+            conditionA.canRestart.emit(true)
+            conditionB.canRestart.emit(false)
 
             restarter.restartSystemUI("Restart for test")
-            advanceUntilIdle()
+            runCurrent()
             // No restart occurs yet.
             verify(systemExitRestarter, never()).restartSystemUI(any())
 
-            conditionB.canRestart = true
-            conditionB.retryFn?.invoke()
-            advanceUntilIdle()
+            conditionB.canRestart.emit(true)
+            runCurrent()
             verify(systemExitRestarter).restartSystemUI(any())
         }
 
     @Test
     fun restart_WaitsForAllConditions() =
         testScope.runTest {
-            conditionA.canRestart = true
-            conditionB.canRestart = false
+            conditionA.canRestart.emit(true)
+            conditionB.canRestart.emit(false)
 
             restarter.restartSystemUI("Restart for test")
-            advanceUntilIdle()
+            runCurrent()
             // No restart occurs yet.
             verify(systemExitRestarter, never()).restartSystemUI(any())
 
             // B becomes true, but A is now false
-            conditionA.canRestart = false
-            conditionB.canRestart = true
-            conditionB.retryFn?.invoke()
-            advanceUntilIdle()
+            conditionA.canRestart.emit(false)
+            conditionB.canRestart.emit(true)
             // No restart occurs yet.
             verify(systemExitRestarter, never()).restartSystemUI(any())
 
-            conditionA.canRestart = true
-            conditionA.retryFn?.invoke()
-            advanceUntilIdle()
+            conditionA.canRestart.emit(true)
+            runCurrent()
             verify(systemExitRestarter).restartSystemUI(any())
         }
 
     class FakeCondition : ConditionalRestarter.Condition {
-        var retryFn: (() -> Unit)? = null
-        var canRestart = false
+        val canRestart = MutableStateFlow(false)
 
-        override fun canRestartNow(retryFn: () -> Unit): Boolean {
-            this.retryFn = retryFn
-
-            return canRestart
-        }
+        override val canRestartNow: Flow<Boolean> = canRestart
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 37c70d8..2bd2bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -43,10 +43,10 @@
     fun accessingUnspecifiedFlags_throwsException() {
         val flags: FeatureFlags = FakeFeatureFlags()
         try {
-            assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+            assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(teamfood)")
+            assertThat(ex.message).contains("UNKNOWN(null_flag)")
         }
         try {
             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 14c5ec0..f51745b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -22,6 +22,7 @@
 import android.content.res.Resources
 import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -36,7 +37,6 @@
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyString
@@ -66,6 +66,7 @@
     private lateinit var broadcastReceiver: BroadcastReceiver
     private lateinit var clearCacheAction: Consumer<String>
     private val serverFlagReader = ServerFlagReaderFake()
+    private val fakeGantryFlags = FakeFeatureFlagsImpl()
 
     private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
     private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
@@ -73,7 +74,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false)
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
         flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
         mFeatureFlagsClassicDebug =
@@ -85,6 +86,7 @@
                 resources,
                 serverFlagReader,
                 flagMap,
+                fakeGantryFlags,
                 restarter
             )
         mFeatureFlagsClassicDebug.init()
@@ -122,8 +124,6 @@
 
     @Test
     fun teamFoodFlag_False() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(false)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -134,8 +134,7 @@
 
     @Test
     fun teamFoodFlag_True() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(true)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -150,8 +149,7 @@
             .thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
             .thenReturn(false)
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(true)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
 
@@ -479,7 +477,7 @@
                 verify(flagManager, times(numReads))
                     .readFlagValue(eq(name), any<FlagSerializer<*>>())
                 verify(flagManager).nameToSettingsKey(eq(name))
-                verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
+                verify(globalSettings).putString(eq("key-$name"), eq(data))
                 verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
             }
             .verifyNoMoreInteractions()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
new file mode 100644
index 0000000..db6f85f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+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.TestDispatcher
+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.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class NotOccludedConditionTest : SysuiTestCase() {
+    private lateinit var condition: NotOccludedCondition
+
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private val transitionValue = MutableStateFlow(0f)
+
+    private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+    private val testScope: TestScope = TestScope(testDispatcher)
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(keyguardTransitionInteractor.transitionValue(KeyguardState.OCCLUDED))
+            .thenReturn(transitionValue)
+        condition = NotOccludedCondition({ keyguardTransitionInteractor })
+        testScope.runCurrent()
+    }
+
+    @Test
+    fun testCondition_occluded() =
+        testScope.runTest {
+            val canRestart by collectLastValue(condition.canRestartNow)
+
+            transitionValue.emit(1f)
+            assertThat(canRestart).isFalse()
+        }
+
+    @Test
+    fun testCondition_notOccluded() =
+        testScope.runTest {
+            val canRestart by collectLastValue(condition.canRestartNow)
+
+            transitionValue.emit(0f)
+            assertThat(canRestart).isTrue()
+        }
+
+    @Test
+    fun testCondition_invokesRetry() =
+        testScope.runTest {
+            val canRestart by collectLastValue(condition.canRestartNow)
+
+            transitionValue.emit(1f)
+
+            assertThat(canRestart).isFalse()
+
+            transitionValue.emit(0f)
+
+            assertThat(canRestart).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 647b05a..7d7abab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -17,8 +17,13 @@
 
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
@@ -35,42 +40,51 @@
     private lateinit var condition: PluggedInCondition
 
     @Mock private lateinit var batteryController: BatteryController
+    private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+    private val testScope: TestScope = TestScope(testDispatcher)
+    private val callbackCaptor =
+        ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        condition = PluggedInCondition(batteryController)
+
+        condition = PluggedInCondition({ batteryController })
     }
 
     @Test
-    fun testCondition_unplugged() {
-        whenever(batteryController.isPluggedIn).thenReturn(false)
+    fun testCondition_unplugged() =
+        testScope.runTest {
+            whenever(batteryController.isPluggedIn).thenReturn(false)
 
-        assertThat(condition.canRestartNow({})).isFalse()
-    }
+            val canRestart by collectLastValue(condition.canRestartNow)
+
+            assertThat(canRestart).isFalse()
+        }
 
     @Test
-    fun testCondition_pluggedIn() {
-        whenever(batteryController.isPluggedIn).thenReturn(true)
+    fun testCondition_pluggedIn() =
+        testScope.runTest {
+            whenever(batteryController.isPluggedIn).thenReturn(true)
 
-        assertThat(condition.canRestartNow({})).isTrue()
-    }
+            val canRestart by collectLastValue(condition.canRestartNow)
+
+            assertThat(canRestart).isTrue()
+        }
 
     @Test
-    fun testCondition_invokesRetry() {
-        whenever(batteryController.isPluggedIn).thenReturn(false)
-        var retried = false
-        val retryFn = { retried = true }
+    fun testCondition_invokesRetry() =
+        testScope.runTest {
+            whenever(batteryController.isPluggedIn).thenReturn(false)
 
-        // No restart yet, but we do register a listener now.
-        assertThat(condition.canRestartNow(retryFn)).isFalse()
-        val captor =
-            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
-        verify(batteryController).addCallback(captor.capture())
+            val canRestart by collectLastValue(condition.canRestartNow)
 
-        whenever(batteryController.isPluggedIn).thenReturn(true)
+            assertThat(canRestart).isFalse()
 
-        captor.value.onBatteryLevelChanged(0, true, true)
-        assertThat(retried).isTrue()
-    }
+            verify(batteryController).addCallback(callbackCaptor.capture())
+
+            callbackCaptor.value.onBatteryLevelChanged(0, true, false)
+
+            assertThat(canRestart).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index f7a773e..1f04828 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -17,15 +17,17 @@
 
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -36,42 +38,50 @@
 class ScreenIdleConditionTest : SysuiTestCase() {
     private lateinit var condition: ScreenIdleCondition
 
-    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var powerInteractor: PowerInteractor
+    private val isAsleep = MutableStateFlow(false)
+
+    private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+    private val testScope: TestScope = TestScope(testDispatcher)
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        condition = ScreenIdleCondition(wakefulnessLifecycle)
+        whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
+        condition = ScreenIdleCondition({ powerInteractor })
     }
 
     @Test
-    fun testCondition_awake() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+    fun testCondition_awake() =
+        testScope.runTest {
+            val canRestart by collectLastValue(condition.canRestartNow)
 
-        assertThat(condition.canRestartNow {}).isFalse()
-    }
+            isAsleep.emit(false)
+
+            assertThat(canRestart).isFalse()
+        }
 
     @Test
-    fun testCondition_asleep() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+    fun testCondition_asleep() =
+        testScope.runTest {
+            val canRestart by collectLastValue(condition.canRestartNow)
 
-        assertThat(condition.canRestartNow {}).isTrue()
-    }
+            isAsleep.emit(true)
+
+            assertThat(canRestart).isTrue()
+        }
 
     @Test
-    fun testCondition_invokesRetry() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
-        var retried = false
-        val retryFn = { retried = true }
+    fun testCondition_invokesRetry() =
+        testScope.runTest {
+            val canRestart by collectLastValue(condition.canRestartNow)
 
-        // No restart yet, but we do register a listener now.
-        assertThat(condition.canRestartNow(retryFn)).isFalse()
-        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
-        verify(wakefulnessLifecycle).addObserver(captor.capture())
+            isAsleep.emit(false)
 
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+            assertThat(canRestart).isFalse()
 
-        captor.value.onFinishedGoingToSleep()
-        assertThat(retried).isTrue()
-    }
+            isAsleep.emit(true)
+
+            assertThat(canRestart).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
new file mode 100644
index 0000000..f4d2cfd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.platform.test.flag.junit.SetFlagsRule
+
+/**
+ * Set the given flag's value to the real value for the current build configuration. This prevents
+ * test code from crashing because it is reading an unspecified flag value.
+ *
+ * REMINDER: You should always test your code with your flag in both configurations, so generally
+ * you should be explicitly enabling or disabling your flag. This method is for situations where the
+ * flag needs to be read (e.g. in the class constructor), but its value shouldn't affect the actual
+ * test cases. In those cases, it's mildly safer to use this method than to hard-code `false` or
+ * `true` because then at least if you're wrong, and the flag value *does* matter, you'll notice
+ * when the flag is flipped and tests start failing.
+ */
+fun SetFlagsRule.setFlagDefault(flagName: String) {
+    if (getFlagDefault(flagName)) {
+        enableFlags(flagName)
+    } else {
+        disableFlags(flagName)
+    }
+}
+
+/**
+ * Set the given flag to an explicit value, or, if null, to the real value for the current build
+ * configuration. This allows for convenient provisioning in tests where certain tests don't care
+ * what the value is (`setFlagValue(FLAG_FOO, null)`), and others want an explicit value.
+ */
+fun SetFlagsRule.setFlagValue(name: String, value: Boolean?) {
+    when (value) {
+        null -> setFlagDefault(name)
+        true -> enableFlags(name)
+        false -> disableFlags(name)
+    }
+}
+
+// NOTE: This code uses reflection to gain access to private members of aconfig generated
+//  classes (in the same way SetFlagsRule does internally) because this is the only way to get
+//  at the underlying information and read the current value of the flag.
+// If aconfig had flag constants with accessible default values, this would be unnecessary.
+private fun getFlagDefault(name: String): Boolean {
+    val flagPackage = name.substringBeforeLast(".")
+    val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl")
+    val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance()
+    val flagMethodName = name.substringAfterLast(".").snakeToCamelCase()
+    val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName)
+    return flagGetter.invoke(featureFlagsImpl) as Boolean
+}
+
+private fun String.snakeToCamelCase(): String {
+    val pattern = "_[a-z]".toRegex()
+    return replace(pattern) { it.value.last().uppercase() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index b1cf051..00009f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -76,8 +76,11 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -105,8 +108,8 @@
     @Mock private LockPatternUtils mLockPatternUtils;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private TelephonyListenerManager mTelephonyListenerManager;
-    @Mock private GlobalSettings mGlobalSettings;
-    @Mock private SecureSettings mSecureSettings;
+    private GlobalSettings mGlobalSettings;
+    private SecureSettings mSecureSettings;
     @Mock private Resources mResources;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private UserTracker mUserTracker;
@@ -132,6 +135,7 @@
     @Mock private ShadeController mShadeController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
 
@@ -148,6 +152,9 @@
         when(mResources.getConfiguration()).thenReturn(
                 getContext().getResources().getConfiguration());
 
+        mGlobalSettings = new FakeGlobalSettings();
+        mSecureSettings = new FakeSettings();
+
         mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
                 mWindowManagerFuncs,
                 mAudioManager,
@@ -181,12 +188,14 @@
                 mPackageManager,
                 mShadeController,
                 mKeyguardUpdateMonitor,
-                mDialogLaunchAnimator);
+                mDialogLaunchAnimator,
+                mSelectedUserInteractor);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
         ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
         backdropColors.setMainColor(Color.BLACK);
         when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(0);
     }
 
     @Test
@@ -563,7 +572,8 @@
     @Test
     public void testOnLockScreen_disableSmartLock() {
         mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int expectedUser = 100;
+        doReturn(expectedUser).when(mSelectedUserInteractor).getSelectedUserId();
         doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
         doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
         doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
@@ -581,7 +591,7 @@
         mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
 
         // Then smart lock will be disabled
-        verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+        verify(mLockPatternUtils).requireCredentialEntry(eq(expectedUser));
 
         // hide dialog again
         mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
@@ -592,8 +602,8 @@
         UserInfo currentUser = mockCurrentUser(FLAG_ADMIN);
 
         when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
-        when(mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU,
-                0, currentUser.id)).thenReturn(1);
+        mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1,
+                currentUser.id);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
@@ -605,8 +615,8 @@
         UserInfo currentUser = mockCurrentUser(0);
 
         when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
-        doReturn(1).when(mGlobalSettings)
-                .getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+        mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1,
+                currentUser.id);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 0ee348e..7750d25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import kotlin.math.max
+import kotlin.test.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -149,26 +151,52 @@
     }
 
     @Test
-    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
-        // GIVEN max velocity and slider progress
-        val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
-        val ticks = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
-        }
+    fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() {
+        // GIVEN max velocity and a slider progress at half progress
+        val firstProgress = 0.5f
+        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+        // Given a second slider progress event smaller than the progress threshold
+        val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
 
         // GIVEN system running for 1s
         clock.advanceTime(1000)
 
-        // WHEN two calls to play occur with the required threshold separation
-        sliderHapticFeedbackProvider.onProgress(progress)
+        // WHEN two calls to play occur with the required threshold separation (time and progress)
+        sliderHapticFeedbackProvider.onProgress(firstProgress)
         clock.advanceTime(dragTextureThresholdMillis.toLong())
-        sliderHapticFeedbackProvider.onProgress(progress)
+        sliderHapticFeedbackProvider.onProgress(secondProgress)
 
-        // THEN the correct composition plays two times
-        verify(vibratorHelper, times(2))
-            .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
+        // THEN Only the first compositions plays
+        verify(vibratorHelper, times(1))
+            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
+        verify(vibratorHelper, times(1))
+            .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java))
+    }
+
+    @Test
+    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
+        // GIVEN max velocity and a slider progress at half progress
+        val firstProgress = 0.5f
+        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+        // Given a second slider progress event beyond progress threshold
+        val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+        val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
+
+        // GIVEN system running for 1s
+        clock.advanceTime(1000)
+
+        // WHEN two calls to play occur with the required threshold separation (time and progress)
+        sliderHapticFeedbackProvider.onProgress(firstProgress)
+        clock.advanceTime(dragTextureThresholdMillis.toLong())
+        sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+        // THEN the correct compositions play
+        verify(vibratorHelper, times(1))
+            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
+        verify(vibratorHelper, times(1))
+            .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))
     }
 
     @Test
@@ -229,6 +257,38 @@
             .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
     }
 
+    fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
+        // GIVEN max velocity and a slider progress at half progress
+        val progress = 0.5f
+
+        // GIVEN system running for 1s
+        clock.advanceTime(1000)
+
+        // WHEN a drag texture plays
+        sliderHapticFeedbackProvider.onProgress(progress)
+
+        // THEN the dragTextureLastProgress remembers the latest progress
+        assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
+    }
+
+    @Test
+    fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() {
+        // GIVEN max velocity and a slider progress at half progress
+        val progress = 0.5f
+
+        // GIVEN system running for 1s
+        clock.advanceTime(1000)
+
+        // WHEN a drag texture plays
+        sliderHapticFeedbackProvider.onProgress(progress)
+
+        // WHEN the handle is released
+        sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+
+        // THEN the dragTextureLastProgress tracker is reset
+        assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
+    }
+
     private fun scaleAtBookends(velocity: Float): Float {
         val range = config.upperBookendScale - config.lowerBookendScale
         val interpolatedVelocity =
@@ -244,4 +304,15 @@
         val bump = interpolatedVelocity * config.additionalVelocityMaxBump
         return interpolatedProgress * range + config.progressBasedDragMinScale + bump
     }
+
+    private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
+        val ticks = VibrationEffect.startComposition()
+        repeat(config.numberOfLowTicks) {
+            ticks.addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                scaleAtProgressChange(velocity, progress)
+            )
+        }
+        return ticks.compose()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
index 632d149..f373062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -16,23 +16,17 @@
 
 package com.android.systemui.keyevent.domain.interactor
 
-import android.view.KeyEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
 import com.google.common.truth.Truth.assertThat
+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.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
@@ -40,108 +34,27 @@
 class KeyEventInteractorTest : SysuiTestCase() {
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
-    private lateinit var keyguardInteractorWithDependencies:
-        KeyguardInteractorFactory.WithDependencies
-    @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
-    @Mock private lateinit var backActionInteractor: BackActionInteractor
+    private lateinit var repository: FakeKeyEventRepository
 
     private lateinit var underTest: KeyEventInteractor
 
     @Before
     fun setup() {
-        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        repository = FakeKeyEventRepository()
         underTest =
             KeyEventInteractor(
-                backActionInteractor,
-                keyguardKeyEventInteractor,
+                repository,
             )
     }
 
     @Test
-    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
-        val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
-        val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() =
+        runTest {
+            val isPowerDown by collectLastValue(underTest.isPowerButtonDown)
+            repository.setPowerButtonDown(false)
+            assertThat(isPowerDown).isFalse()
 
-        // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
-            .thenReturn(false)
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
-            .thenReturn(false)
-
-        // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
-        assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
-        // THEN back event isn't handled on ACTION_DOWN
-        verify(backActionInteractor, never()).onBackRequested()
-
-        // WHEN back key event ACTION_UP
-        assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
-        // THEN back event is handled on ACTION_UP
-        verify(backActionInteractor).onBackRequested()
-    }
-
-    @Test
-    fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
-        assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
-    }
-
-    @Test
-    fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
-        assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
-    }
-
-    @Test
-    fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
-        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
-    }
-
-    @Test
-    fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
-        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
-    }
-
-    @Test
-    fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
-        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
-    }
-
-    @Test
-    fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
-        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
-    }
+            repository.setPowerButtonDown(true)
+            assertThat(isPowerDown).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
new file mode 100644
index 0000000..af00a48
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIKeyEventHandlerTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var keyguardInteractorWithDependencies:
+        KeyguardInteractorFactory.WithDependencies
+    @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+    private lateinit var underTest: SysUIKeyEventHandler
+
+    @Before
+    fun setup() {
+        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        underTest =
+            SysUIKeyEventHandler(
+                backActionInteractor,
+                keyguardKeyEventInteractor,
+            )
+    }
+
+    @Test
+    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
+        val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
+        val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+
+        // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
+            .thenReturn(false)
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
+            .thenReturn(false)
+
+        // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
+        // THEN back event isn't handled on ACTION_DOWN
+        verify(backActionInteractor, never()).onBackRequested()
+
+        // WHEN back key event ACTION_UP
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
+        // THEN back event is handled on ACTION_UP
+        verify(backActionInteractor).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 7a13a0a..489665c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -106,7 +106,6 @@
 
         whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
         whenever(powerManager.isInteractive).thenReturn(true)
-        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
 
         // All of these fields are final, so we can't mock them, but are needed so that the surface
         // appear amount setter doesn't short circuit.
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 a646823..2b280c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -117,6 +117,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -187,6 +188,7 @@
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
     private @Mock ShadeInteractor mShadeInteractor;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
+    private @Mock SelectedUserInteractor mSelectedUserInteractor;
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallback;
     private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -213,7 +215,7 @@
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
 
     private FakeFeatureFlags mFeatureFlags;
-    private int mInitialUserId;
+    private final int mDefaultUserId = 100;
 
     @Before
     public void setUp() throws Exception {
@@ -233,6 +235,8 @@
                 .thenReturn(mock(Flow.class));
         when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
                 .thenReturn(mock(Flow.class));
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
+        when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
                 new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -251,9 +255,11 @@
                 mAuthController,
                 mShadeExpansionStateManager,
                 () -> mShadeInteractor,
-                mShadeWindowLogger);
+                mShadeWindowLogger,
+                () -> mSelectedUserInteractor);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
+        mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
 
         DejankUtils.setImmediate(true);
 
@@ -266,12 +272,6 @@
         }).when(mKeyguardStateController).notifyKeyguardGoingAway(anyBoolean());
 
         createAndStartViewMediator();
-        mInitialUserId = KeyguardUpdateMonitor.getCurrentUser();
-    }
-
-    @After
-    public void teardown() {
-        KeyguardUpdateMonitor.setCurrentUser(mInitialUserId);
     }
 
     /**
@@ -451,7 +451,7 @@
         mViewMediator.setKeyguardEnabled(false);
         TestableLooper.get(this).processAllMessages();
 
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -617,8 +617,8 @@
     public void lockAfterScreenTimeoutUsesValueFromSettings() {
         int currentUserId = 99;
         int userSpecificTimeout = 5999;
-        KeyguardUpdateMonitor.setCurrentUser(currentUserId);
 
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId);
         when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
         when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
         when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
@@ -718,7 +718,7 @@
         startMockKeyguardExitAnimation();
         assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
 
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         TestableLooper.get(this).processAllMessages();
         verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
@@ -782,7 +782,7 @@
 
         // Verify keyguard told of authentication
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -814,7 +814,7 @@
         // Verify keyguard told of authentication
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
         clearInvocations(mStatusBarKeyguardViewManager);
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -844,7 +844,7 @@
 
         // Verify keyguard told of authentication
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -1111,7 +1111,8 @@
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
                 mSystemPropertiesHelper,
-                () -> mock(WindowManagerLockscreenVisibilityManager.class));
+                () -> mock(WindowManagerLockscreenVisibilityManager.class),
+                mSelectedUserInteractor);
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index faf9751..977f1db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -25,13 +25,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.EnableZenModeDialog
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 1e80fb6..26fcb23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -22,8 +22,8 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.util.FakeSharedPreferences
@@ -70,8 +70,7 @@
         val resources: Resources = mock()
         whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
             .thenReturn(emptyArray())
-        whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled))
-            .thenReturn(true)
+        whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)).thenReturn(true)
         whenever(context.resources).thenReturn(resources)
 
         testDispatcher = UnconfinedTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index d8cdf29..90fd652 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -147,7 +147,7 @@
             emptyMap()
         )
         verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
-        verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
+        verify(authController, times(2)).addCallback(authControllerCallback.capture())
     }
 
     @Test
@@ -314,18 +314,18 @@
     fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
             faceAuthIsEnabledByBiometricManager()
 
             doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
 
             runCurrent()
-            clearInvocations(authController)
 
-            whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
-            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+            enrollmentChange(FACE, PRIMARY_USER_ID, false)
 
             assertThat(faceAuthAllowed()).isFalse()
-            verify(authController).addCallback(authControllerCallback.capture())
+
             enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
 
             assertThat(faceAuthAllowed()).isFalse()
@@ -375,25 +375,20 @@
     fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
             verify(biometricManager)
                 .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
 
             userRepository.setSelectedUserInfo(ANOTHER_USER)
             doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
             biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
-
-            runCurrent()
-            clearInvocations(authController)
-
-            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
-            runCurrent()
-
-            verify(authController).addCallback(authControllerCallback.capture())
-
+            onNonStrongAuthChanged(true, ANOTHER_USER_ID)
             whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
             enrollmentChange(FACE, ANOTHER_USER_ID, true)
+            runCurrent()
 
-            assertThat(faceAuthAllowed()).isTrue()
+            assertThat(faceAuthAllowed).isTrue()
         }
 
     @Test
@@ -637,6 +632,7 @@
             val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
 
             faceAuthIsEnrolled()
+            enrollmentChange(FACE, PRIMARY_USER_ID, true)
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
@@ -660,6 +656,7 @@
             val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
 
             faceAuthIsEnrolled()
+            enrollmentChange(FACE, PRIMARY_USER_ID, true)
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsNonStrongBiometric()
@@ -753,7 +750,9 @@
         }
 
     private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
-        authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
+        authControllerCallback.allValues.forEach {
+            it.onEnrollmentsChanged(biometricType, userId, enabled)
+        }
     }
 
     private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 819d08a..9bb2434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -43,7 +43,6 @@
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -82,6 +81,7 @@
 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.res.R
 import com.android.systemui.statusbar.phone.FakeKeyguardStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
@@ -94,6 +94,8 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestDispatcher
@@ -116,8 +118,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -195,9 +195,11 @@
             }
 
         powerRepository = FakePowerRepository()
-        powerInteractor = PowerInteractorFactory.create(
-                repository = powerRepository,
-        ).powerInteractor
+        powerInteractor =
+            PowerInteractorFactory.create(
+                    repository = powerRepository,
+                )
+                .powerInteractor
 
         val withDeps =
             KeyguardInteractorFactory.create(
@@ -210,10 +212,10 @@
 
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         keyguardTransitionInteractor =
-                KeyguardTransitionInteractorFactory.create(
-                        scope = testScope.backgroundScope,
-                        repository = keyguardTransitionRepository,
-                        keyguardInteractor = keyguardInteractor,
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = keyguardTransitionRepository,
+                    keyguardInteractor = keyguardInteractor,
                 )
                 .keyguardTransitionInteractor
 
@@ -635,11 +637,7 @@
 
     @Test
     fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth {
-                powerInteractor.setAsleepForTest()
-            }
-        }
+        testScope.runTest { testGatingCheckForFaceAuth { powerInteractor.setAsleepForTest() } }
 
     @Test
     fun authenticateDoesNotRunWhenSecureCameraIsActive() =
@@ -736,17 +734,21 @@
             allPreconditionsToRunFaceAuthAreTrue()
 
             Log.i("TEST", "started waking")
-            keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
                     from = KeyguardState.LOCKSCREEN,
                     to = KeyguardState.OFF,
                     transitionState = TransitionState.FINISHED,
-            ))
+                )
+            )
             runCurrent()
-            keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
                     from = KeyguardState.OFF,
                     to = KeyguardState.LOCKSCREEN,
                     transitionState = TransitionState.STARTED,
-            ))
+                )
+            )
             runCurrent()
 
             Log.i("TEST", "sending display off")
@@ -766,11 +768,13 @@
         testScope.runTest {
             testGatingCheckForFaceAuth {
                 powerInteractor.onFinishedWakingUp()
-                keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
                         from = KeyguardState.OFF,
                         to = KeyguardState.LOCKSCREEN,
                         transitionState = TransitionState.FINISHED,
-                ))
+                    )
+                )
                 runCurrent()
 
                 displayRepository.emit(
@@ -919,11 +923,7 @@
 
     @Test
     fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
-        testScope.runTest {
-            testGatingCheckForDetect {
-                powerInteractor.setAsleepForTest()
-            }
-        }
+        testScope.runTest { testGatingCheckForDetect { powerInteractor.setAsleepForTest() } }
 
     @Test
     fun detectDoesNotRunWhenSecureCameraIsActive() =
@@ -1114,6 +1114,32 @@
             biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
             faceAuthenticateIsCalled()
         }
+    @Test
+    fun authFailedCallAfterAuthLockedOutErrorShouldBeIgnored() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            runCurrent()
+            assertThat(canFaceAuthRun()).isTrue()
+
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false)
+            runCurrent()
+
+            faceAuthenticateIsCalled()
+            authenticationCallback.value.onAuthenticationError(
+                FACE_ERROR_LOCKOUT_PERMANENT,
+                "Too many attempts, face not available"
+            )
+
+            val lockoutError = authStatus() as ErrorFaceAuthenticationStatus
+            assertThat(lockedOut()).isTrue()
+            assertThat(lockoutError.isLockoutError()).isTrue()
+
+            authenticationCallback.value.onAuthenticationFailed()
+            runCurrent()
+
+            assertThat(authStatus()).isEqualTo(lockoutError)
+        }
 
     private suspend fun TestScope.testGatingCheckForFaceAuth(
         gatingCheckModifier: suspend () -> Unit
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9119e1..6c4bb37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -30,14 +30,10 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
-import com.android.systemui.keyguard.ScreenLifecycle
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +43,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -68,13 +63,10 @@
 
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var screenLifecycle: ScreenLifecycle
-    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
-    @Mock private lateinit var dozeParameters: DozeParameters
     private val mainDispatcher = StandardTestDispatcher()
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -89,12 +81,9 @@
         underTest =
             KeyguardRepositoryImpl(
                 statusBarStateController,
-                screenLifecycle,
-                biometricUnlockController,
                 keyguardStateController,
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
-                dozeParameters,
                 authController,
                 dreamOverlayCallbackController,
                 mainDispatcher,
@@ -201,26 +190,6 @@
         }
 
     @Test
-    fun isAodAvailable() = runTest {
-        val flow = underTest.isAodAvailable
-        val isAodAvailable = collectLastValue(flow)
-        runCurrent()
-
-        val callback =
-            withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) }
-
-        whenever(dozeParameters.getAlwaysOn()).thenReturn(false)
-        callback.onAlwaysOnChange()
-        assertThat(isAodAvailable()).isEqualTo(false)
-
-        whenever(dozeParameters.getAlwaysOn()).thenReturn(true)
-        callback.onAlwaysOnChange()
-        assertThat(isAodAvailable()).isEqualTo(true)
-
-        flow.onCompletion { verify(dozeParameters).removeCallback(callback) }
-    }
-
-    @Test
     fun isKeyguardOccluded() =
         testScope.runTest {
             whenever(keyguardStateController.isOccluded).thenReturn(false)
@@ -247,6 +216,29 @@
         }
 
     @Test
+    fun isKeyguardUnlocked() =
+        testScope.runTest {
+            whenever(keyguardStateController.isUnlocked).thenReturn(false)
+            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
+
+            runCurrent()
+            assertThat(isKeyguardUnlocked).isFalse()
+
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
+
+            whenever(keyguardStateController.isUnlocked).thenReturn(true)
+            captor.value.onUnlockedChanged()
+            runCurrent()
+            assertThat(isKeyguardUnlocked).isTrue()
+
+            whenever(keyguardStateController.isUnlocked).thenReturn(false)
+            captor.value.onKeyguardShowingChanged()
+            runCurrent()
+            assertThat(isKeyguardUnlocked).isFalse()
+        }
+
+    @Test
     fun isDozing() =
         testScope.runTest {
             underTest.setIsDozing(true)
@@ -386,53 +378,6 @@
         }
 
     @Test
-    fun biometricUnlockState() =
-        testScope.runTest {
-            val values = mutableListOf<BiometricUnlockModel>()
-            val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
-
-            runCurrent()
-            val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>()
-            verify(biometricUnlockController).addListener(captor.capture())
-
-            listOf(
-                    BiometricUnlockController.MODE_NONE,
-                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
-                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
-                    BiometricUnlockController.MODE_SHOW_BOUNCER,
-                    BiometricUnlockController.MODE_ONLY_WAKE,
-                    BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
-                    BiometricUnlockController.MODE_DISMISS_BOUNCER,
-                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
-                )
-                .forEach {
-                    whenever(biometricUnlockController.mode).thenReturn(it)
-                    captor.value.onModeChanged(it)
-                    runCurrent()
-                }
-
-            assertThat(values)
-                .isEqualTo(
-                    listOf(
-                        // Initial value will be NONE, followed by onModeChanged() call
-                        BiometricUnlockModel.NONE,
-                        BiometricUnlockModel.NONE,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
-                        BiometricUnlockModel.SHOW_BOUNCER,
-                        BiometricUnlockModel.ONLY_WAKE,
-                        BiometricUnlockModel.UNLOCK_COLLAPSING,
-                        BiometricUnlockModel.DISMISS_BOUNCER,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
-                    )
-                )
-
-            job.cancel()
-            runCurrent()
-            verify(biometricUnlockController).removeListener(captor.value)
-        }
-
-    @Test
     fun dozeTransitionModel() =
         testScope.runTest {
             // For the initial state
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5afc405..ad80a06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.KeyguardTransitionRunner
@@ -86,7 +87,7 @@
         }
 
     @Test
-    fun startingSecondTransitionWillCancelTheFirstTransition() =
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseLastValue() =
         TestScope().runTest {
             val steps = mutableListOf<TransitionStep>()
             val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
@@ -100,12 +101,19 @@
             val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
             runner.startTransition(
                 this,
-                TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.LAST_VALUE
+                ),
             )
 
             val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
             assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
 
+            // Second transition starts from .1 (LAST_VALUE)
             val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
             assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
 
@@ -114,6 +122,76 @@
         }
 
     @Test
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReset() =
+        TestScope().runTest {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 3,
+            )
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.RESET
+                ),
+            )
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            // Second transition starts from 0 (RESET)
+            val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReverse() =
+        TestScope().runTest {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 3,
+            )
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.REVERSE
+                ),
+            )
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            // Second transition starts from .9 (REVERSE)
+            val secondTransitionSteps = listWithStep(start = BigDecimal(0.9), step = BigDecimal(.1))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+        }
+
+    @Test
     fun nullAnimatorEnablesManualControlWithUpdateTransition() =
         TestScope().runTest {
             val steps = mutableListOf<TransitionStep>()
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 5bd747f..8dea57c 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
@@ -20,10 +20,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 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.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.mock
 import dagger.Lazy
 import junit.framework.Assert.assertEquals
@@ -42,6 +46,12 @@
 class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
     private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
 
+    private val mSelectedUserInteractor =
+        SelectedUserInteractor(
+            FakeUserRepository(),
+            FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
+        )
+
     // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
     // underTest interactor is provided to any classes that need it.
     override var fromPrimaryBouncerTransitionInteractorLazy:
@@ -63,6 +73,7 @@
                 flags = FakeFeatureFlags(),
                 keyguardSecurityModel = mock(),
                 powerInteractor = PowerInteractorFactory.create().powerInteractor,
+                selectedUserInteractor = mSelectedUserInteractor
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index d6e19cb..e87adf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -64,8 +64,6 @@
             KeyguardDismissInteractorFactory.create(
                 context = context,
                 testScope = testScope,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-                dispatcher = dispatcher,
             )
         keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
         transitionRepository = FakeKeyguardTransitionRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
index a5cfbbf..ecb46bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -58,8 +58,6 @@
             KeyguardDismissInteractorFactory.create(
                 context = context,
                 testScope = testScope,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-                dispatcher = dispatcher,
             )
         underTest = underTestDependencies.interactor
         underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 06eb0dd..e45f56a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -43,7 +43,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+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.FakeKeyguardRepository
@@ -62,6 +62,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -95,9 +96,11 @@
         FakeDeviceEntryFingerprintAuthRepository
     private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
     private lateinit var powerInteractor: PowerInteractor
+    private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Before
     fun setup() {
@@ -123,6 +126,8 @@
         facePropertyRepository = FakeFacePropertyRepository()
         fakeKeyguardRepository = FakeKeyguardRepository()
         powerInteractor = PowerInteractorFactory.create().powerInteractor
+        fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
+
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
                 mContext,
@@ -142,12 +147,13 @@
                     keyguardUpdateMonitor,
                     FakeTrustRepository(),
                     testScope.backgroundScope,
+                    mSelectedUserInteractor,
                 ),
                 AlternateBouncerInteractor(
                     mock(StatusBarStateController::class.java),
                     mock(KeyguardStateController::class.java),
                     bouncerRepository,
-                    mock(BiometricSettingsRepository::class.java),
+                    fakeBiometricSettingsRepository,
                     FakeSystemClock(),
                     keyguardUpdateMonitor,
                 ),
@@ -160,6 +166,7 @@
                 facePropertyRepository,
                 faceWakeUpTriggersConfig,
                 powerInteractor,
+                fakeBiometricSettingsRepository,
             )
     }
 
@@ -481,6 +488,7 @@
     fun faceUnlockIsDisabledWhenFpIsLockedOut() =
         testScope.runTest {
             underTest.start()
+            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
 
             fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 9ee22c8..27325d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -68,7 +68,6 @@
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
             featureFlags = featureFlags,
             sceneContainerFlags = testUtils.sceneContainerFlags,
-            deviceEntryRepository = testUtils.deviceEntryRepository,
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
             shadeRepository = shadeRepository,
@@ -204,8 +203,7 @@
                     fromScene = SceneKey.Gone,
                     toScene = SceneKey.Lockscreen,
                     progress = flowOf(0f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             runCurrent()
             assertThat(isAnimate).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index 900413c..6cd7406 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -59,8 +59,6 @@
         KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
     private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
 
-    private lateinit var keyguardInteractorWithDependencies:
-        KeyguardInteractorFactory.WithDependencies
     private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -75,14 +73,12 @@
     fun setup() {
         whenever(mediaSessionLegacyHelperWrapper.getHelper(any()))
             .thenReturn(mediaSessionLegacyHelper)
-        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
         powerInteractor = PowerInteractorFactory.create().powerInteractor
 
         underTest =
             KeyguardKeyEventInteractor(
                 context,
                 statusBarStateController,
-                keyguardInteractorWithDependencies.keyguardInteractor,
                 statusBarKeyguardViewManager,
                 shadeController,
                 mediaSessionLegacyHelperWrapper,
@@ -164,7 +160,6 @@
     fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() {
         powerInteractor.setAwakeForTest()
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false)
 
         verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE)
     }
@@ -180,22 +175,12 @@
     @Test
     fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() {
         powerInteractor.setAwakeForTest()
-        whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
         verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER)
     }
 
     @Test
-    fun dispatchKeyEvent_enterActionUp_awakeKeyguard_primaryBouncerAlreadyShowing() {
-        powerInteractor.setAwakeForTest()
-        whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(true)
-        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-
-        verifyActionsDoNothing(KeyEvent.KEYCODE_ENTER)
-    }
-
-    @Test
     fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() {
         powerInteractor.setAwakeForTest()
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
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 2cf0e77..275ac80 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
@@ -42,6 +42,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.model.ShadeModel
+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.util.mockito.withArgCaptor
@@ -56,7 +57,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
@@ -86,6 +86,7 @@
 
     // Used to verify transition requests for test output
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
     private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@@ -158,6 +159,7 @@
                     flags = featureFlags,
                     keyguardSecurityModel = keyguardSecurityModel,
                     powerInteractor = powerInteractor,
+                    selectedUserInteractor = mSelectedUserInteractor,
                 )
                 .apply { start() }
 
@@ -241,7 +243,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -268,7 +270,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -295,7 +297,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -325,7 +327,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -356,7 +358,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -383,7 +385,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -410,7 +412,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -433,7 +435,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the lockscreen hosted dream stops
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -441,7 +445,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to Lockscreen should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -457,7 +461,9 @@
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN biometrics succeeds with wake and unlock from dream mode
             keyguardRepository.setBiometricUnlockState(
@@ -467,7 +473,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to Gone should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -487,7 +493,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the primary bouncer is set to show
             bouncerRepository.setPrimaryShow(true)
@@ -495,7 +503,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -515,7 +523,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the device begins to sleep
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -526,7 +536,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -547,7 +557,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the keyguard is occluded and the lockscreen hosted dream stops
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -556,7 +568,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -580,7 +592,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -612,7 +624,7 @@
             advanceUntilIdle()
 
             // THEN the transition is ignored
-            verify(transitionRepository, never()).startTransition(any(), anyBoolean())
+            verify(transitionRepository, never()).startTransition(any())
 
             coroutineContext.cancelChildren()
         }
@@ -629,7 +641,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -656,7 +668,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -683,7 +695,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -706,7 +718,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -736,7 +748,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -767,7 +779,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -783,7 +795,9 @@
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // WHEN the alternateBouncer stops showing and then the primary bouncer shows
             bouncerRepository.setPrimaryShow(true)
@@ -791,7 +805,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -808,7 +822,9 @@
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // GIVEN the primary bouncer isn't showing, aod available and starting to sleep
             bouncerRepository.setPrimaryShow(false)
@@ -821,7 +837,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -838,7 +854,9 @@
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep
             // to sleep
@@ -852,7 +870,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -869,7 +887,9 @@
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // GIVEN the primary bouncer isn't showing and device not sleeping
             bouncerRepository.setPrimaryShow(false)
@@ -880,7 +900,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -908,7 +928,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -936,7 +956,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -960,7 +980,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -980,7 +1000,9 @@
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
             bouncerRepository.setPrimaryShow(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER)
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                KeyguardState.PRIMARY_BOUNCER
+            )
 
             // WHEN the primary bouncer stops showing and lockscreen hosted dream still active
             bouncerRepository.setPrimaryShow(false)
@@ -988,7 +1010,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1019,7 +1041,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to GONE should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1048,7 +1070,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1073,7 +1095,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1098,7 +1120,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1124,7 +1146,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1149,7 +1171,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -1161,6 +1183,57 @@
         }
 
     @Test
+    fun dreamingToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreaming(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN the keyguard is occluded and device wakes up and is no longer dreaming
+            keyguardRepository.setDreaming(false)
+            keyguardRepository.setKeyguardOccluded(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun lockscreenToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to LOCKSCREEN
+            runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+            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("FromLockscreenTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun aodToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to AOD
@@ -1179,7 +1252,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
@@ -1213,7 +1286,7 @@
             // THEN a transition from LOCKSCREEN => OCCLUDED should occur
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1244,7 +1317,7 @@
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1265,7 +1338,7 @@
             // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
             val info2 =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
             assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1286,8 +1359,8 @@
     }
 
     private suspend fun TestScope.runTransitionAndSetWakefulness(
-            from: KeyguardState,
-            to: KeyguardState
+        from: KeyguardState,
+        to: KeyguardState
     ) {
         transitionRepository.sendTransitionStep(
             TransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 02c98cd..f9362a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -90,6 +91,7 @@
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mockedContext: Context
     @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Before
     fun setup() {
@@ -145,6 +147,7 @@
                     keyguardUpdateMonitor,
                     trustRepository,
                     testScope.backgroundScope,
+                    mSelectedUserInteractor
                 ),
                 AlternateBouncerInteractor(
                     statusBarStateController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 7940b45..8cfa87d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -23,14 +23,15 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
@@ -54,7 +55,7 @@
     private lateinit var underTest: DefaultKeyguardBlueprint
     private lateinit var rootView: KeyguardRootView
     @Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection
-    @Mock private lateinit var defaultLockIconSection: DefaultLockIconSection
+    @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection
     @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
     @Mock
     private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
@@ -65,6 +66,7 @@
     @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
     @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
     @Mock private lateinit var aodBurnInSection: AodBurnInSection
+    @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection
 
     @Before
     fun setup() {
@@ -73,7 +75,7 @@
         underTest =
             DefaultKeyguardBlueprint(
                 defaultIndicationAreaSection,
-                defaultLockIconSection,
+                mDefaultDeviceEntryIconSection,
                 defaultShortcutsSection,
                 defaultAmbientIndicationAreaSection,
                 defaultSettingsPopupMenuSection,
@@ -83,6 +85,7 @@
                 splitShadeGuidelines,
                 aodNotificationIconsSection,
                 aodBurnInSection,
+                communalTutorialIndicatorSection,
             )
     }
 
@@ -98,14 +101,14 @@
         val prevBlueprint = mock(KeyguardBlueprint::class.java)
         val someSection = mock(KeyguardSection::class.java)
         whenever(prevBlueprint.sections)
-            .thenReturn(underTest.sections.minus(defaultLockIconSection).plus(someSection))
+            .thenReturn(underTest.sections.minus(mDefaultDeviceEntryIconSection).plus(someSection))
         val constraintLayout = ConstraintLayout(context, null)
         underTest.replaceViews(prevBlueprint, constraintLayout)
-        underTest.sections.minus(defaultLockIconSection).forEach {
+        underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach {
             verify(it, never()).addViews(constraintLayout)
         }
 
-        verify(defaultLockIconSection).addViews(constraintLayout)
+        verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout)
         verify(someSection).removeViews(constraintLayout)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
similarity index 65%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
index 7495637..5f22c7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
@@ -24,14 +24,17 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -40,35 +43,54 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
 @SmallTest
-class DefaultLockIconSectionTest : SysuiTestCase() {
+class DefaultDeviceEntryIconSectionTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var authController: AuthController
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
     @Mock private lateinit var notificationPanelView: NotificationPanelView
-    @Mock private lateinit var featureFlags: FeatureFlags
+    private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var lockIconViewController: LockIconViewController
-    private lateinit var underTest: DefaultLockIconSection
+    @Mock private lateinit var falsingManager: FalsingManager
+    private lateinit var underTest: DefaultDeviceEntryIconSection
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        featureFlags =
+            FakeFeatureFlagsClassic().apply {
+                set(Flags.MIGRATE_LOCK_ICON, false)
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false)
+                set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+            }
         underTest =
-            DefaultLockIconSection(
+            DefaultDeviceEntryIconSection(
                 keyguardUpdateMonitor,
                 authController,
                 windowManager,
                 context,
                 notificationPanelView,
                 featureFlags,
-                lockIconViewController
+                { lockIconViewController },
+                { DeviceEntryIconViewModel() },
+                { falsingManager },
             )
     }
 
     @Test
-    fun addViewsConditionally() {
-        whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(true)
+    fun addViewsConditionally_migrateFlagOn() {
+        featureFlags.set(Flags.MIGRATE_LOCK_ICON, true)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isGreaterThan(0)
+    }
+
+    @Test
+    fun addViewsConditionally_migrateAndRefactorFlagsOn() {
+        featureFlags.set(Flags.MIGRATE_LOCK_ICON, true)
+        featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
         val constraintLayout = ConstraintLayout(context, null)
         underTest.addViews(constraintLayout)
         assertThat(constraintLayout.childCount).isGreaterThan(0)
@@ -76,7 +98,8 @@
 
     @Test
     fun addViewsConditionally_migrateFlagOff() {
-        whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(false)
+        featureFlags.set(Flags.MIGRATE_LOCK_ICON, false)
+        featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false)
         val constraintLayout = ConstraintLayout(context, null)
         underTest.addViews(constraintLayout)
         assertThat(constraintLayout.childCount).isEqualTo(0)
@@ -87,18 +110,18 @@
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
 
-        val constraint = cs.getConstraint(R.id.lock_icon_view)
+        val constraint = cs.getConstraint(R.id.device_entry_icon_view)
 
         assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
         assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
     }
 
     @Test
-    fun testCenterLockIcon() {
+    fun testCenterIcon() {
         val cs = ConstraintSet()
-        underTest.centerLockIcon(Point(5, 6), 1F, cs)
+        underTest.centerIcon(Point(5, 6), 1F, cs)
 
-        val constraint = cs.getConstraint(R.id.lock_icon_view)
+        val constraint = cs.getConstraint(R.id.device_entry_icon_view)
 
         assertThat(constraint.layout.mWidth).isEqualTo(2)
         assertThat(constraint.layout.mHeight).isEqualTo(2)
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
new file mode 100644
index 0000000..1ff46db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerToGoneFlowsTest : SysuiTestCase() {
+    private lateinit var underTest: BouncerToGoneFlows
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    @Mock
+    private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
+
+    private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansionStateFlow)
+
+        repository = FakeKeyguardTransitionRepository()
+        val featureFlags =
+            FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
+        underTest =
+            BouncerToGoneFlows(
+                interactor,
+                statusBarStateController,
+                primaryBouncerInteractor,
+                keyguardDismissActionInteractor,
+                featureFlags,
+                shadeInteractor,
+            )
+
+        whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+        whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+    }
+
+    @Test
+    fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            shadeExpansionStateFlow.value = 1f
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+            values.forEach { assertThat(it.notificationsAlpha).isIn(Range.closed(0f, 1f)) }
+        }
+
+    @Test
+    fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            shadeExpansionStateFlow.value = 0f
+
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
+        }
+
+    @Test
+    fun scrimBehindAlpha_leaveShadeOpen() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+
+            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach {
+                assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+            }
+        }
+
+    @Test
+    fun scrimBehindAlpha_doNotLeaveShadeOpen() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+
+            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+            assertThat(values[3].behindAlpha).isEqualTo(0f)
+        }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.PRIMARY_BOUNCER,
+            to = KeyguardState.GONE,
+            value = value,
+            transitionState = state,
+            ownerName = "PrimaryBouncerToGoneTransitionViewModelTest"
+        )
+    }
+}
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
new file mode 100644
index 0000000..255f4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+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
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GoneToAodTransitionViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: GoneToAodTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        repository = FakeKeyguardTransitionRepository()
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
+        underTest = GoneToAodTransitionViewModel(interactor)
+    }
+
+    @Test
+    fun enterFromTopTranslationY() =
+        testScope.runTest {
+            val pixels = -100f
+            val enterFromTopTranslationY by
+                collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+
+            // The animation should only start > halfway through
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+            repository.sendTransitionStep(step(.85f))
+            assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+
+            // At the end, the translation should be complete and set to zero
+            repository.sendTransitionStep(step(1f))
+            assertThat(enterFromTopTranslationY).isEqualTo(0f)
+        }
+
+    @Test
+    fun enterFromTopAnimationAlpha() =
+        testScope.runTest {
+            val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+
+            // The animation should only start > halfway through
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+            repository.sendTransitionStep(step(.85f))
+            assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f))
+
+            repository.sendTransitionStep(step(1f))
+            assertThat(enterFromTopAnimationAlpha).isEqualTo(1f)
+        }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.GONE,
+            to = KeyguardState.AOD,
+            value = value,
+            transitionState = state,
+            ownerName = "GoneToAodTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 71688db..4f545cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -17,8 +17,10 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.view.View
 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.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -26,12 +28,17 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import javax.inject.Provider
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -41,6 +48,7 @@
 import org.junit.runners.JUnit4
 import org.mockito.Answers
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -51,10 +59,20 @@
     private lateinit var testScope: TestScope
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var configurationRepository: FakeConfigurationRepository
     @Mock private lateinit var burnInInteractor: BurnInInteractor
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+    @Mock
+    private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
 
     private val burnInFlow = MutableStateFlow(BurnInModel())
+    private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0)
+    private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+    private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+    private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+    private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE)
 
     @Before
     fun setUp() {
@@ -71,9 +89,30 @@
         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
         keyguardInteractor = withDeps.keyguardInteractor
         repository = withDeps.repository
+        configurationRepository = withDeps.configurationRepository
+
+        whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+            .thenReturn(emptyFlow<Float>())
+        whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+            .thenReturn(enterFromTopAnimationAlpha)
 
         whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
-        underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor)
+
+        whenever(keyguardTransitionInteractor.goneToAodTransition)
+            .thenReturn(goneToAodTransitionStep)
+        whenever(keyguardTransitionInteractor.dozeAmountTransition)
+            .thenReturn(dozeAmountTransitionStep)
+        whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState)
+
+        underTest =
+            KeyguardRootViewModel(
+                context,
+                keyguardInteractor,
+                burnInInteractor,
+                goneToAodTransitionViewModel,
+                aodToLockscreenTransitionViewModel,
+                keyguardTransitionInteractor,
+            )
         underTest.clockControllerProvider = Provider { clockController }
     }
 
@@ -118,7 +157,7 @@
             val scale by collectLastValue(underTest.scale)
 
             // Set to not dozing (on lockscreen)
-            repository.setDozeAmount(0f)
+            dozeAmountTransitionStep.emit(TransitionStep(value = 0f))
 
             // Trigger a change to the burn-in model
             burnInFlow.value =
@@ -141,8 +180,7 @@
             val scale by collectLastValue(underTest.scale)
 
             // Set to dozing (on AOD)
-            repository.setDozeAmount(1f)
-
+            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
             // Trigger a change to the burn-in model
             burnInFlow.value =
                 BurnInModel(
@@ -150,10 +188,15 @@
                     translationY = 30,
                     scale = 0.5f,
                 )
-
             assertThat(translationX).isEqualTo(20)
             assertThat(translationY).isEqualTo(30)
             assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+            // Set to the beginning of GONE->AOD transition
+            goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
         }
 
     @Test
@@ -166,7 +209,7 @@
             val scale by collectLastValue(underTest.scale)
 
             // Set to dozing (on AOD)
-            repository.setDozeAmount(1f)
+            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
 
             // Trigger a change to the burn-in model
             burnInFlow.value =
@@ -180,4 +223,28 @@
             assertThat(translationY).isEqualTo(0)
             assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
         }
+
+    @Test
+    fun burnInLayerVisibility() =
+        testScope.runTest {
+            val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+            startedKeyguardState.value = KeyguardState.OCCLUDED
+            assertThat(burnInLayerVisibility).isNull()
+
+            startedKeyguardState.value = KeyguardState.AOD
+            assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun burnInLayerAlpha() =
+        testScope.runTest {
+            val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
+
+            enterFromTopAnimationAlpha.value = 0.2f
+            assertThat(burnInLayerAlpha).isEqualTo(0.2f)
+
+            enterFromTopAnimationAlpha.value = 1f
+            assertThat(burnInLayerAlpha).isEqualTo(1f)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 9364097..6cab023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -35,6 +34,7 @@
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -52,18 +52,19 @@
     private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    @Mock private lateinit var bouncerToGoneFlows: BouncerToGoneFlows
     @Mock
     private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
 
+    private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
         repository = FakeKeyguardTransitionRepository()
         val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
-                set(Flags.UDFPS_NEW_TOUCH_DETECTION, true)
-            }
+            FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
         val interactor =
             KeyguardTransitionInteractorFactory.create(
                     scope = TestScope().backgroundScope,
@@ -77,6 +78,7 @@
                 primaryBouncerInteractor,
                 keyguardDismissActionInteractor,
                 featureFlags,
+                bouncerToGoneFlows,
             )
 
         whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
@@ -151,59 +153,6 @@
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
-    @Test
-    fun scrimAlpha_runDimissFromKeyguard() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values by collectValues(underTest.scrimAlpha)
-
-            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
-
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            repository.sendTransitionStep(step(0.3f))
-            repository.sendTransitionStep(step(0.6f))
-            repository.sendTransitionStep(step(1f))
-
-            assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
-        }
-
-    @Test
-    fun scrimBehindAlpha_leaveShadeOpen() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values by collectValues(underTest.scrimAlpha)
-
-            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
-
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            repository.sendTransitionStep(step(0.3f))
-            repository.sendTransitionStep(step(0.6f))
-            repository.sendTransitionStep(step(1f))
-
-            assertThat(values.size).isEqualTo(4)
-            values.forEach {
-                assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
-            }
-        }
-
-    @Test
-    fun scrimBehindAlpha_doNotLeaveShadeOpen() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values by collectValues(underTest.scrimAlpha)
-
-            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
-
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            repository.sendTransitionStep(step(0.3f))
-            repository.sendTransitionStep(step(0.6f))
-            repository.sendTransitionStep(step(1f))
-
-            assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
-            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
-            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
-            assertThat(values[3].behindAlpha).isEqualTo(0f)
-        }
-
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 54fc493..1abb441 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -42,7 +42,7 @@
     private var frameCount = 1L
     private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
     private var job: Job? = null
-    private var isTerminated = false
+    @Volatile private var isTerminated = false
 
     /**
      * For transitions being directed by an animator. Will control the number of frames being
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index fb0a4be..59d8104 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -48,7 +48,6 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.InstanceIdSequenceFake
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
@@ -63,6 +62,7 @@
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -2204,6 +2204,85 @@
     }
 
     @Test
+    fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control with PlaybackState actions is added, times out,
+        // and then the session is destroyed
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        mediaDataManager.setTimedOut(KEY, timedOut = true)
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is fully removed.
+        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+        verify(listener, never())
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+    }
+
+    @Test
+    fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions is added, and then the session is destroyed
+        // without timing out first
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is fully removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+        verify(listener, never())
+            .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+    }
+
+    @Test
+    fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions and that does allow resumption is added,
+        addNotificationAndLoad()
+        val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+        // And then the session is destroyed without timing out first
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 85d3fba..b101acf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
+import android.media.MediaRoute2Info
 import android.media.MediaRouter2Manager
 import android.media.RoutingSessionInfo
 import android.media.session.MediaController
@@ -34,15 +35,18 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
-import com.android.systemui.res.R
+import com.android.settingslib.media.PhoneMediaDevice
 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.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
@@ -95,6 +99,7 @@
     @Mock private lateinit var device: MediaDevice
     @Mock private lateinit var icon: Drawable
     @Mock private lateinit var route: RoutingSessionInfo
+    @Mock private lateinit var selectedRoute: MediaRoute2Info
     @Mock private lateinit var controller: MediaController
     @Mock private lateinit var playbackInfo: PlaybackInfo
     @Mock private lateinit var configurationController: ConfigurationController
@@ -107,6 +112,7 @@
     private lateinit var session: MediaSession
     private lateinit var mediaData: MediaData
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private val featureFlags = FakeFeatureFlagsClassic()
 
     @Before
     fun setUp() {
@@ -118,13 +124,14 @@
                 context,
                 controllerFactory,
                 lmmFactory,
-                mr2,
+                { mr2 },
                 muteAwaitFactory,
                 configurationController,
-                localBluetoothManager,
+                { localBluetoothManager },
                 fakeFgExecutor,
                 fakeBgExecutor,
-                dumpster
+                dumpster,
+                featureFlags,
             )
         manager.addListener(listener)
 
@@ -143,6 +150,7 @@
             MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
         whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
         setupLeAudioConfiguration(false)
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false)
     }
 
     @After
@@ -454,9 +462,54 @@
     }
 
     @Test
-    fun mr2ReturnsRouteWithNullName_useLocalDeviceName() {
+    fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // When the routing session name is null, and is a system session for a PhoneMediaDevice
+        val phoneDevice = mock(PhoneMediaDevice::class.java)
+        whenever(phoneDevice.iconWithoutBackground).thenReturn(icon)
+        whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice)
+        whenever(route.isSystemSession).thenReturn(true)
+
+        whenever(route.name).thenReturn(null)
+        whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute))
+        whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
+        whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)
+
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+
+        // Then the device name is the PhoneMediaDevice string
+        val data = captureDeviceData(KEY)
+        assertThat(data.name)
+            .isEqualTo(
+                context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
+            )
+    }
+
+    @Test
+    fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // When the routing session does not have a name, and is a system session
+        whenever(route.name).thenReturn(null)
+        whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute))
+        whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
+        whenever(route.isSystemSession).thenReturn(true)
+
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+
+        // Then the device name is the selected route name
+        val data = captureDeviceData(KEY)
+        assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
+    }
+
+    @Test
+    fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() {
         // GIVEN that MR2Manager returns a routing session that does not have a name
         whenever(route.name).thenReturn(null)
+        whenever(route.isSystemSession).thenReturn(false)
         // WHEN a notification is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
         fakeBgExecutor.runAllReady()
@@ -672,13 +725,108 @@
         assertThat(data.showBroadcastButton).isFalse()
     }
 
-    fun captureCallback(): LocalMediaManager.DeviceCallback {
+    // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled
+
+    @Test
+    fun loadMediaDataWithNullToken_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+    }
+
+    @Test
+    fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        // Run and reset the executors and listeners so we only focus on new events.
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+
+        // Ensure we'll get device info when using the address
+        val fullMediaDevice = mock(MediaDevice::class.java)
+        val address = "fakeAddress"
+        val nameFromDevice = "nameFromDevice"
+        val iconFromDevice = mock(Drawable::class.java)
+        whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice)
+        whenever(fullMediaDevice.name).thenReturn(nameFromDevice)
+        whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice)
+
+        // WHEN the about-to-connect device changes to non-null
+        val deviceCallback = captureCallback()
+        val nameFromParam = "nameFromParam"
+        val iconFromParam = mock(Drawable::class.java)
+        deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+
+        // THEN the about-to-connect device based on the address is returned
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(nameFromDevice)
+        assertThat(data.name).isNotEqualTo(nameFromParam)
+        assertThat(data.icon).isEqualTo(iconFromDevice)
+        assertThat(data.icon).isNotEqualTo(iconFromParam)
+    }
+
+    @Test
+    fun deviceNameFromMR2RouteInfo_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // GIVEN that MR2Manager returns a valid routing session
+        whenever(route.name).thenReturn(REMOTE_DEVICE_NAME)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN it uses the route name (instead of device name)
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
+    }
+
+    @Test
+    fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // GIVEN that MR2Manager returns null for routing session
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is disabled and name is set to null
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isNull()
+    }
+
+    @Test
+    fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // GIVEN that MR2Manager returns a routing session that does not have a name
+        whenever(route.name).thenReturn(null)
+        whenever(route.isSystemSession).thenReturn(false)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is enabled and uses the current connected device name
+        val data = captureDeviceData(KEY)
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+        assertThat(data.enabled).isTrue()
+    }
+
+    // End duplicate tests
+
+    private fun captureCallback(): LocalMediaManager.DeviceCallback {
         val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
         verify(lmm).registerCallback(captor.capture())
         return captor.getValue()
     }
 
-    fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
+    private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
         val callback: BluetoothLeBroadcast.Callback =
             object : BluetoothLeBroadcast.Callback {
                 override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
@@ -699,7 +847,7 @@
         return callback
     }
 
-    fun setupLeAudioConfiguration(isLeAudio: Boolean) {
+    private fun setupLeAudioConfiguration(isLeAudio: Boolean) {
         whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
         whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
             .thenReturn(localBluetoothLeBroadcast)
@@ -707,7 +855,7 @@
         whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
     }
 
-    fun setupBroadcastPackage(currentName: String) {
+    private fun setupBroadcastPackage(currentName: String) {
         whenever(lmm.packageName).thenReturn(PACKAGE)
         whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
             .thenReturn(applicationInfo)
@@ -715,7 +863,7 @@
         context.setMockPackageManager(packageManager)
     }
 
-    fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
+    private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
         val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
         verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
         return captor.getValue()
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 de57b60..5296f1a 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
@@ -24,7 +24,6 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
@@ -32,6 +31,7 @@
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
new file mode 100644
index 0000000..da448aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionMetricsLoggerTest : SysuiTestCase() {
+
+    private val service = mock<IMediaProjectionManager>()
+    private val logger = MediaProjectionMetricsLogger(service)
+
+    @Test
+    fun notifyProjectionInitiated_sourceApp_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.APP
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_APP)
+    }
+
+    @Test
+    fun notifyProjectionInitiated_sourceCast_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.CAST
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_CAST)
+    }
+
+    @Test
+    fun notifyProjectionInitiated_sourceSysUI_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service)
+            .notifyPermissionRequestInitiated(
+                hostUid,
+                METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+            )
+    }
+
+    @Test
+    fun notifyProjectionInitiated_sourceUnknown_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.UNKNOWN
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_UNKNOWN)
+    }
+
+    @Test
+    fun notifyPermissionRequestDisplayed_forwardsToService() {
+        val hostUid = 987
+
+        logger.notifyPermissionRequestDisplayed(hostUid)
+
+        verify(service).notifyPermissionRequestDisplayed(hostUid)
+    }
+
+    @Test
+    fun notifyProjectionCancelled_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+
+        logger.notifyProjectionRequestCancelled(hostUid)
+
+        verify(service).notifyPermissionRequestCancelled(hostUid)
+    }
+
+    @Test
+    fun notifyAppSelectorDisplayed_forwardsToService() {
+        val hostUid = 654
+
+        logger.notifyAppSelectorDisplayed(hostUid)
+
+        verify(service).notifyAppSelectorDisplayed(hostUid)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index f25cd24..44798ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -5,17 +5,23 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.shared.recents.model.ThumbnailData
 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.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+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.verify
 
 @RunWith(AndroidTestingRunner::class)
@@ -33,8 +39,11 @@
 
     private val view: MediaProjectionAppSelectorView = mock()
     private val policyResolver: ScreenCaptureDevicePolicyResolver = mock()
+    private val logger = mock<MediaProjectionMetricsLogger>()
 
-    private val controller =
+    private val thumbnailLoader = FakeThumbnailLoader()
+
+    private fun createController(isFirstStart: Boolean = true, hostUid: Int = 123) =
         MediaProjectionAppSelectorController(
             taskListProvider,
             view,
@@ -42,7 +51,11 @@
             personalUserHandle,
             scope,
             appSelectorComponentName,
-            callerPackageName
+            callerPackageName,
+            thumbnailLoader,
+            isFirstStart,
+            logger,
+            hostUid,
         )
 
     @Before
@@ -54,7 +67,7 @@
     fun initNoRecentTasks_bindsEmptyList() {
         taskListProvider.tasks = emptyList()
 
-        controller.init()
+        createController().init()
 
         verify(view).bind(emptyList())
     }
@@ -63,12 +76,28 @@
     fun initOneRecentTask_bindsList() {
         taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
 
-        controller.init()
+        createController().init()
 
         verify(view).bind(listOf(createRecentTask(taskId = 1)))
     }
 
     @Test
+    fun init_refreshesThumbnailsOfForegroundTasks() = runTest {
+        val tasks =
+            listOf(
+                createRecentTask(taskId = 1, isForegroundTask = false),
+                createRecentTask(taskId = 2, isForegroundTask = true),
+                createRecentTask(taskId = 3, isForegroundTask = true),
+                createRecentTask(taskId = 4, isForegroundTask = false),
+            )
+        taskListProvider.tasks = tasks
+
+        createController().init()
+
+        assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)
+    }
+
+    @Test
     fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
         val tasks =
             listOf(
@@ -78,7 +107,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -101,7 +130,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -124,7 +153,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -149,7 +178,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -176,11 +205,40 @@
         taskListProvider.tasks = tasks
 
         givenCaptureAllowed(isAllow = false)
-        controller.init()
+        createController().init()
 
         verify(view).bind(emptyList())
     }
 
+    @Test
+    fun init_firstStart_logsAppSelectorDisplayed() {
+        val hostUid = 123456789
+        val controller = createController(isFirstStart = true, hostUid)
+
+        controller.init()
+
+        verify(logger).notifyAppSelectorDisplayed(hostUid)
+    }
+
+    @Test
+    fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
+        val hostUid = 123456789
+        val controller = createController(isFirstStart = false, hostUid)
+
+        controller.init()
+
+        verify(logger, never()).notifyAppSelectorDisplayed(hostUid)
+    }
+
+    @Test
+    fun onSelectorDismissed_logsProjectionRequestCancelled() {
+        val hostUid = 123
+
+        createController(hostUid = hostUid).onSelectorDismissed()
+
+        verify(logger).notifyProjectionRequestCancelled(hostUid)
+    }
+
     private fun givenCaptureAllowed(isAllow: Boolean) {
         whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)
     }
@@ -188,14 +246,17 @@
     private fun createRecentTask(
         taskId: Int,
         topActivityComponent: ComponentName? = null,
-        userId: Int = personalUserHandle.identifier
+        userId: Int = personalUserHandle.identifier,
+        isForegroundTask: Boolean = false
     ): RecentTask {
         return RecentTask(
             taskId = taskId,
+            displayId = 0,
             topActivityComponent = topActivityComponent,
             baseIntentComponent = ComponentName("com", "Test"),
             userId = userId,
-            colorBackground = 0
+            colorBackground = 0,
+            isForegroundTask = isForegroundTask,
         )
     }
 
@@ -205,4 +266,18 @@
 
         override suspend fun loadRecentTasks(): List<RecentTask> = tasks
     }
+
+    private class FakeThumbnailLoader : RecentTaskThumbnailLoader {
+
+        val capturedTaskIds = mutableListOf<Int>()
+
+        override suspend fun loadThumbnail(taskId: Int): ThumbnailData? {
+            return null
+        }
+
+        override suspend fun captureThumbnail(taskId: Int): ThumbnailData? {
+            capturedTaskIds += taskId
+            return null
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
new file mode 100644
index 0000000..db275ec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -0,0 +1,109 @@
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.res.Configuration
+import android.graphics.ColorSpace
+import android.graphics.Point
+import android.graphics.Rect
+import android.hardware.HardwareBuffer
+import android.testing.AndroidTestingRunner
+import android.view.Surface
+import android.window.TaskSnapshot
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
+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.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val activityManager = mock<ActivityManagerWrapper>()
+    private val loader = ActivityTaskManagerThumbnailLoader(dispatcher, activityManager)
+
+    @Test
+    fun loadThumbnail_emptyThumbnail_returnsNull() =
+        testScope.runTest {
+            val taskId = 123
+            val isLowResolution = false
+            val thumbnailData = ThumbnailData()
+            whenever(activityManager.getTaskThumbnail(taskId, isLowResolution))
+                .thenReturn(thumbnailData)
+
+            assertThat(loader.loadThumbnail(taskId)).isNull()
+        }
+
+    @Test
+    fun loadThumbnail_thumbnailAvailable_returnsThumbnailData() =
+        testScope.runTest {
+            val taskId = 123
+            val isLowResolution = false
+            val snapshot = createTaskSnapshot()
+            val thumbnailData = ThumbnailData(snapshot)
+            whenever(activityManager.getTaskThumbnail(taskId, isLowResolution))
+                .thenReturn(thumbnailData)
+
+            assertThat(loader.loadThumbnail(taskId)).isEqualTo(thumbnailData)
+        }
+
+    @Test
+    fun captureThumbnail_emptyThumbnail_returnsNull() =
+        testScope.runTest {
+            val taskId = 321
+            val emptyThumbnailData = ThumbnailData()
+
+            whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(emptyThumbnailData)
+
+            assertThat(loader.captureThumbnail(taskId)).isNull()
+        }
+
+    @Test
+    fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() =
+        testScope.runTest {
+            val taskId = 321
+            val thumbnailData = ThumbnailData(createTaskSnapshot())
+
+            whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData)
+
+            assertThat(loader.captureThumbnail(taskId)).isEqualTo(thumbnailData)
+        }
+
+    private fun createTaskSnapshot() =
+        TaskSnapshot(
+            /* id= */ 123,
+            /* captureTime= */ 0,
+            /* topActivityComponent= */ ComponentName("package", "class"),
+            /* snapshot= */ HardwareBuffer.create(
+                /* width= */ 100,
+                /* height= */ 100,
+                HardwareBuffer.RGBA_8888,
+                /* layers= */ 1,
+                /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN
+            ),
+            ColorSpace.get(ColorSpace.Named.SRGB),
+            Configuration.ORIENTATION_PORTRAIT,
+            Surface.ROTATION_0,
+            /* taskSize= */ Point(100, 100),
+            /* contentInsets= */ Rect(),
+            /* letterboxInsets= */ Rect(),
+            /* isLowResolution= */ false,
+            /* isRealSnapshot= */ true,
+            WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+            /* appearance= */ 0,
+            /* isTranslucent= */ false,
+            /* hasImeSurface= */ false
+        )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index d35a212..d75553f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -11,7 +11,7 @@
 import com.android.wm.shell.recents.RecentTasks
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import com.google.common.truth.Truth.assertThat
-import java.util.*
+import java.util.Optional
 import java.util.function.Consumer
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
@@ -52,12 +52,7 @@
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
 
-        assertThat(result)
-            .containsExactly(
-                createRecentTask(taskId = 1),
-                createRecentTask(taskId = 2),
-                createRecentTask(taskId = 3),
-            )
+        assertThat(result.map { it.taskId }).containsExactly(1, 2, 3).inOrder()
     }
 
     @Test
@@ -66,8 +61,7 @@
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
 
-        assertThat(result)
-            .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2))
+        assertThat(result.map { it.taskId }).containsExactly(1, 2).inOrder()
     }
 
     @Test
@@ -81,15 +75,46 @@
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
 
-        assertThat(result)
-            .containsExactly(
-                createRecentTask(taskId = 1),
-                createRecentTask(taskId = 2),
-                createRecentTask(taskId = 3),
-                createRecentTask(taskId = 4),
-                createRecentTask(taskId = 5),
-                createRecentTask(taskId = 6),
-            )
+        assertThat(result.map { it.taskId }).containsExactly(1, 2, 3, 4, 5, 6).inOrder()
+    }
+
+    @Test
+    fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result[0].isForegroundTask).isFalse()
+    }
+
+    @Test
+    fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createSingleTask(taskId = 2),
+            createSingleTask(taskId = 3),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.isForegroundTask }).containsExactly(false, true, false).inOrder()
+    }
+
+    @Test
+    fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createTaskPair(taskId1 = 2, taskId2 = 3),
+            createSingleTask(taskId = 4),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.isForegroundTask })
+            .containsExactly(false, true, true, false)
+            .inOrder()
     }
 
     @Suppress("UNCHECKED_CAST")
@@ -103,10 +128,12 @@
     private fun createRecentTask(taskId: Int): RecentTask =
         RecentTask(
             taskId = taskId,
+            displayId = 0,
             userId = 0,
             topActivityComponent = null,
             baseIntentComponent = null,
-            colorBackground = null
+            colorBackground = null,
+            isForegroundTask = false,
         )
 
     private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 906420d..9630ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -46,6 +46,7 @@
     private val mockContext = mock<Context>()
     private val resources = mock<Resources>()
     private val windowManager = mock<WindowManager>()
+    private val windowMetricsProvider = mock<WindowMetricsProvider>()
     private val sizeUpdates = arrayListOf<Rect>()
     private val testConfigurationController = FakeConfigurationController()
 
@@ -112,13 +113,12 @@
     }
 
     private fun givenTaskbarSize(size: Int) {
-        val windowInsets =
-            WindowInsets.Builder()
-                .setInsets(Type.tappableElement(), Insets.of(Rect(0, 0, 0, size)))
-                .build()
+        val insets = Insets.of(Rect(0, 0, 0, size))
+        val windowInsets = WindowInsets.Builder().setInsets(Type.tappableElement(), insets).build()
         val windowMetrics = WindowMetrics(windowManager.maximumWindowMetrics.bounds, windowInsets)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+        whenever(windowMetricsProvider.currentWindowInsets).thenReturn(insets)
     }
 
     private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
@@ -126,6 +126,7 @@
         val windowMetrics = WindowMetrics(bounds, { null }, 1.0f)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+        whenever(windowMetricsProvider.maximumWindowBounds).thenReturn(bounds)
 
         val minDimension = min(width, height)
 
@@ -147,7 +148,11 @@
                 }
             }
 
-        return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController)
+        return TaskPreviewSizeProvider(
+                mockContext,
+                windowMetricsProvider,
+                testConfigurationController
+            )
             .also { it.addCallback(listener) }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
new file mode 100644
index 0000000..fe18454
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.mediaprojection.appselector.view
+
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.core.view.WindowInsetsCompat
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class WindowMetricsProviderImplTest : SysuiTestCase() {
+
+    private val windowManager = mock<WindowManager>()
+    private val windowMetricsProvider = WindowMetricsProviderImpl(windowManager)
+
+    @Test
+    fun getMaximumWindowBounds_returnsValueFromWMMaxWindowMetrics() {
+        val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
+        val metrics =
+            WindowMetrics(bounds, /* windowInsetsSupplier= */ { null }, /* density= */ 1.0f)
+        whenever(windowManager.maximumWindowMetrics).thenReturn(metrics)
+
+        assertThat(windowMetricsProvider.maximumWindowBounds).isEqualTo(bounds)
+    }
+
+    @Test
+    fun getCurrentWindowInsets_returnsFromWMCurrentWindowMetrics() {
+        val bounds = Rect()
+        val insets =
+            Insets.of(Rect(/* left= */ 123, /* top= */ 456, /* right= */ 789, /* bottom= */ 1012))
+        val windowInsets =
+            android.view.WindowInsets.Builder()
+                .setInsets(WindowInsetsCompat.Type.tappableElement(), insets)
+                .build()
+        whenever(windowManager.currentWindowMetrics).thenReturn(WindowMetrics(bounds, windowInsets))
+
+        assertThat(windowMetricsProvider.currentWindowInsets).isEqualTo(insets)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 8019fb5..6248bb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -57,7 +57,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import org.junit.Before;
@@ -77,7 +77,7 @@
     public static final String FORMATTED_45M = "0h 45m";
     public static final String FORMATTED_HOUR = "1h 0m";
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
-    private final GlobalSettings mGlobalSettings = new FakeSettings();
+    private final GlobalSettings mGlobalSettings = new FakeGlobalSettings();
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
index 4be6890..8f06fe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
@@ -35,7 +35,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class SettingObserverTest : SysuiTestCase() {
+class UserSettingObserverTest : SysuiTestCase() {
 
     companion object {
         private const val TEST_SETTING = "setting"
@@ -46,7 +46,7 @@
     }
 
     private lateinit var testableLooper: TestableLooper
-    private lateinit var setting: SettingObserver
+    private lateinit var setting: UserSettingObserver
     private lateinit var secureSettings: SecureSettings
 
     private lateinit var callback: Callback
@@ -56,17 +56,19 @@
         testableLooper = TestableLooper.get(this)
         secureSettings = FakeSettings()
 
-        setting = object : SettingObserver(
-                secureSettings,
-                Handler(testableLooper.looper),
-                TEST_SETTING,
-                USER,
-                DEFAULT_VALUE
-        ) {
-            override fun handleValueChanged(value: Int, observedChange: Boolean) {
-                callback(value, observedChange)
+        setting =
+            object :
+                UserSettingObserver(
+                    secureSettings,
+                    Handler(testableLooper.looper),
+                    TEST_SETTING,
+                    USER,
+                    DEFAULT_VALUE
+                ) {
+                override fun handleValueChanged(value: Int, observedChange: Boolean) {
+                    callback(value, observedChange)
+                }
             }
-        }
 
         // Default empty callback
         callback = { _, _ -> Unit }
@@ -162,4 +164,4 @@
         setting.isListening = true
         assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 43cf1b5..ae47a7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -37,7 +37,6 @@
 import android.view.IWindowManager
 import android.view.View
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.view.LaunchableFrameLayout
@@ -48,6 +47,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -343,8 +343,7 @@
         testableLooper.processAllMessages()
 
         verify(activityStarter, never())
-            .startPendingIntentDismissingKeyguard(
-                any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+            .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
     }
 
     @Test
@@ -357,8 +356,7 @@
         testableLooper.processAllMessages()
 
         verify(activityStarter, never())
-            .startPendingIntentDismissingKeyguard(
-                any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+            .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
     }
 
     @Test
@@ -373,7 +371,7 @@
         testableLooper.processAllMessages()
 
         verify(activityStarter)
-            .startPendingIntentDismissingKeyguard(
+            .startPendingIntentMaybeDismissingKeyguard(
                 eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 67587e3..6cc52d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -29,12 +29,14 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
 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.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -73,16 +75,18 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TileLifecycleManagerTest extends SysuiTestCase {
-    private static final int TEST_FAIL_TIMEOUT = 5000;
 
     private final PackageManagerAdapter mMockPackageManagerAdapter =
             mock(PackageManagerAdapter.class);
     private final BroadcastDispatcher mMockBroadcastDispatcher =
             mock(BroadcastDispatcher.class);
     private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class);
+    private final ActivityManager mActivityManager = mock(ActivityManager.class);
+
     private ComponentName mTileServiceComponentName;
     private Intent mTileServiceIntent;
     private UserHandle mUser;
+    private FakeSystemClock mClock;
     private FakeExecutor mExecutor;
     private HandlerThread mThread;
     private Handler mHandler;
@@ -112,13 +116,15 @@
         mThread = new HandlerThread("TestThread");
         mThread.start();
         mHandler = Handler.createAsync(mThread.getLooper());
-        mExecutor = new FakeExecutor(new FakeSystemClock());
+        mClock = new FakeSystemClock();
+        mExecutor = new FakeExecutor(mClock);
         mStateManager = new TileLifecycleManager(mHandler, mWrappedContext,
                 mock(IQSService.class),
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
     }
 
@@ -294,11 +300,32 @@
         mStateManager.onStartListening();
         mStateManager.executeSetBindService(true);
         mExecutor.runAllReady();
-        mStateManager.setBindRetryDelay(0);
+        mStateManager.onServiceDisconnected(mTileServiceComponentName);
+        mClock.advanceTime(5000);
+
+        // Two calls: one for the first bind, one for the restart.
+        verifyBind(2);
+        verify(mMockTileService, times(2)).onStartListening();
+    }
+
+    @Test
+    public void testKillProcessLowMemory() throws Exception {
+        doAnswer(invocation -> {
+            ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0);
+            memoryInfo.lowMemory = true;
+            return null;
+        }).when(mActivityManager).getMemoryInfo(any());
+        mStateManager.onStartListening();
+        mStateManager.executeSetBindService(true);
         mExecutor.runAllReady();
         mStateManager.onServiceDisconnected(mTileServiceComponentName);
-        mExecutor.runAllReady();
 
+        // Longer delay than a regular one
+        mClock.advanceTime(5000);
+        verifyBind(1);
+        verify(mMockTileService, times(1)).onStartListening();
+
+        mClock.advanceTime(20000);
         // Two calls: one for the first bind, one for the restart.
         verifyBind(2);
         verify(mMockTileService, times(2)).onStartListening();
@@ -319,6 +346,7 @@
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
 
         manager.executeSetBindService(true);
@@ -340,6 +368,7 @@
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
 
         manager.executeSetBindService(true);
@@ -361,6 +390,7 @@
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
 
         manager.executeSetBindService(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 4bc16a5..d011821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -304,7 +304,7 @@
                 CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
                     commandQueue, statusBarIconController, panelInteractor,
-                    customTileAddedRepository, executor);
+                    mTileLifecycleManagerFactory, customTileAddedRepository, executor);
         }
 
         @Override
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 4ada44c..c711806 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
@@ -25,7 +25,6 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.settingslib.drawable.UserIconDrawable
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.ContentDescription
@@ -35,8 +34,8 @@
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.FooterActionsTestUtils
 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
+import com.android.systemui.res.R
 import com.android.systemui.security.data.model.SecurityModel
-import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.policy.FakeSecurityController
 import com.android.systemui.statusbar.policy.FakeUserInfoController
 import com.android.systemui.statusbar.policy.FakeUserInfoController.FakeInfo
@@ -44,7 +43,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
@@ -128,9 +127,8 @@
     fun userSwitcher() = runTest {
         val picture: Drawable = mock()
         val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
-        val settings = FakeSettings()
+        val settings = FakeGlobalSettings()
         val userId = 42
-        val userTracker = FakeUserTracker(userId)
         val userSwitcherControllerWrapper =
             MockUserSwitcherControllerWrapper(currentUserName = "foo")
 
@@ -148,7 +146,6 @@
                     utils.footerActionsInteractor(
                         userSwitcherRepository =
                             utils.userSwitcherRepository(
-                                userTracker = userTracker,
                                 settings = settings,
                                 userManager = userManager,
                                 userInfoController = userInfoController,
@@ -167,14 +164,9 @@
         // for the current user will be fired to notify us of that change.
         isUserSwitcherEnabled = true
 
-        // Update the setting for a random user: nothing should change, given that at this point we
-        // weren't notified of the change yet.
-        utils.setUserSwitcherEnabled(settings, true, 3)
-        assertThat(currentUserSwitcher()).isNull()
-
         // Update the setting for the observed user: now we will be notified and the button should
         // be there.
-        utils.setUserSwitcherEnabled(settings, true, userId)
+        utils.setUserSwitcherEnabled(settings, true)
         val userSwitcher = currentUserSwitcher()
         assertThat(userSwitcher).isNotNull()
         assertThat(userSwitcher!!.icon)
@@ -372,9 +364,7 @@
                     ),
             )
 
-        val job = launch {
-            underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock())
-        }
+        val job = launch { underTest.observeDeviceMonitoringDialogRequests(mock()) }
 
         advanceUntilIdle()
         assertThat(nDialogRequests).isEqualTo(3)
@@ -383,18 +373,6 @@
     }
 
     @Test
-    fun isVisible() {
-        val underTest = utils.footerActionsViewModel()
-        assertThat(underTest.isVisible.value).isFalse()
-
-        underTest.onVisibilityChangeRequested(visible = true)
-        assertThat(underTest.isVisible.value).isTrue()
-
-        underTest.onVisibilityChangeRequested(visible = false)
-        assertThat(underTest.isVisible.value).isFalse()
-    }
-
-    @Test
     fun alpha_inSplitShade_followsExpansion() {
         val underTest = utils.footerActionsViewModel()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced..0e4b113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -124,11 +124,44 @@
     }
 
     @Test
-    fun testLongClickIntent() {
+    fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        val cameraTile = CameraToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        cameraTile.destroy()
+        testableLooper.processAllMessages()
+    }
 
+    @Test
+    fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+        val cameraTile = CameraToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
         assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        cameraTile.destroy()
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index e537131..4c5a214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -54,11 +54,7 @@
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
 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.pipeline.wifi.shared.model.WifiNetworkModel;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -111,7 +107,8 @@
 
     private WifiInteractor mWifiInteractor;
     private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
-    private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+    private final FakeConnectivityRepository mConnectivityRepository =
+            new FakeConnectivityRepository();
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
 
@@ -124,12 +121,6 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-
-        mWifiInteractor = new WifiInteractorImpl(
-                new FakeConnectivityRepository(),
-                mWifiRepository,
-                mTestScope
-        );
     }
 
     @After
@@ -204,25 +195,41 @@
     // SIGNAL_CALLBACK_DEPRECATION flag set to true
 
     @Test
-    public void stateUnavailable_wifiDisabled_newPipeline() {
+    public void stateUnavailable_noDefaultNetworks_newPipeline() {
         createAndStartTileNewImpl();
-        mWifiRepository.setIsWifiEnabled(false);
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
     @Test
-    public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+    public void stateUnavailable_mobileConnected_newPipeline() {
         createAndStartTileNewImpl();
-        mWifiRepository.setIsWifiEnabled(true);
-        mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+        mConnectivityRepository.setMobileConnected(true);
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
     @Test
+    public void stateInactive_wifiConnected_newPipeline() {
+        createAndStartTileNewImpl();
+        mConnectivityRepository.setWifiConnected(true);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void stateInactive_ethernetConnected_newPipeline() {
+        createAndStartTileNewImpl();
+        mConnectivityRepository.setEthernetConnected(true);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
     public void stateActive_wifiConnectedAndCasting_newPipeline() {
         createAndStartTileNewImpl();
         CastController.CastDevice device = new CastController.CastDevice();
@@ -231,40 +238,27 @@
         devices.add(device);
         when(mController.getCastDevices()).thenReturn(devices);
 
-        mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
-                        1 /* networkId */,
-                        true /* isValidated */,
-                        3 /* level */,
-                        "test" /* ssid */,
-                        WifiNetworkModel.HotspotDeviceType.NONE,
-                        false /* isPasspointAccessPoint */,
-                        false /* isOnlineSignUpforPasspointAccessPoint */,
-                        null /* passpointProviderFriendlyName */
-                ));
+        mConnectivityRepository.setWifiConnected(true);
+
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     @Test
-    public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+    public void stateActive_ethernetConnectedAndCasting_newPipeline() {
         createAndStartTileNewImpl();
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
 
-        mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
-                        1 /* networkId */,
-                        true /* isValidated */,
-                        3 /* level */,
-                        "test" /* ssid */,
-                        WifiNetworkModel.HotspotDeviceType.NONE,
-                        false /* isPasspointAccessPoint */,
-                        false /* isOnlineSignUpforPasspointAccessPoint */,
-                        null /* passpointProviderFriendlyName */
-                ));
+        mConnectivityRepository.setEthernetConnected(true);
+
         mTestableLooper.processAllMessages();
 
-        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     // -------------------------------------------------
@@ -512,7 +506,7 @@
                 mNetworkController,
                 mHotspotController,
                 mDialogLaunchAnimator,
-                mWifiInteractor,
+                mConnectivityRepository,
                 mJavaAdapter,
                 mFeatureFlags
         );
@@ -555,7 +549,7 @@
                 mNetworkController,
                 mHotspotController,
                 mDialogLaunchAnimator,
-                mWifiInteractor,
+                mConnectivityRepository,
                 mJavaAdapter,
                 mFeatureFlags
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index 0fcfdb6..b98a7570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -123,11 +123,44 @@
     }
 
     @Test
-    fun testLongClickIntent() {
+    fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        val micTile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        micTile.destroy()
+        testableLooper.processAllMessages()
+    }
 
+    @Test
+    fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        val micTile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        micTile.destroy()
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5b3d45b..c6d156f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -22,11 +22,13 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 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.app.Dialog;
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
@@ -35,11 +37,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -48,7 +50,9 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -89,6 +93,12 @@
     private PanelInteractor mPanelInteractor;
     @Mock
     private QsEventLogger mUiEventLogger;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    @Mock
+    private Dialog mPermissionDialogPrompt;
+    @Mock
+    private UserContextProvider mUserContextProvider;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -99,6 +109,7 @@
 
         mTestableLooper = TestableLooper.get(this);
 
+        when(mUserContextProvider.getUserContext()).thenReturn(mContext);
         when(mHost.getContext()).thenReturn(mContext);
 
         mTile = new ScreenRecordTile(
@@ -116,7 +127,9 @@
                 mKeyguardDismissUtil,
                 mKeyguardStateController,
                 mDialogLaunchAnimator,
-                mPanelInteractor
+                mPanelInteractor,
+                mMediaProjectionMetricsLogger,
+                mUserContextProvider
         );
 
         mTile.initialize();
@@ -280,4 +293,28 @@
         assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
     }
 
+    @Test
+    public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(false);
+        when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+                .thenReturn(mPermissionDialogPrompt);
+
+        mTile.handleClick(null /* view */);
+        mTestableLooper.processAllMessages();
+
+        verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+                eq(mDialogLaunchAnimator), eq(mActivityStarter), any());
+        var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
+        verify(mKeyguardDismissUtil).executeWhenUnlocked(
+                onDismissAction.capture(), anyBoolean(), anyBoolean());
+        assertNotNull(onDismissAction.getValue());
+
+        onDismissAction.getValue().onDismiss();
+
+        verify(mPermissionDialogPrompt).show();
+        verify(mMediaProjectionMetricsLogger)
+                .notifyPermissionRequestDisplayed(mContext.getUserId());
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index 5659f01..95ee3b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -32,16 +32,16 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
 
     @Mock private lateinit var activityStarted: ActivityStarter
 
-    lateinit var underTest: QSTileIntentUserActionHandler
+    lateinit var underTest: QSTileIntentUserInputHandler
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        underTest = QSTileIntentUserActionHandler(activityStarted)
+        underTest = QSTileIntentUserInputHandler(activityStarted)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a6199c2..2bdc154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -83,7 +83,7 @@
     @Test
     fun testDisabledWhenAdminWithNoRestrictions() =
         testScope.runTest {
-            val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+            val admin = EnforcedAdmin(TEST_COMPONENT_NAME, TEST_USER)
             whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin)
             whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
                 .thenReturn(false)
@@ -129,11 +129,11 @@
     }
 
     private companion object {
-        const val TEST_USER = 1
+
         const val TEST_RESTRICTION = "test_restriction"
 
         val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls")
-
-        val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+        val TEST_USER = UserHandle(1)
+        val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, TEST_USER)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index f1fcee3..31d02ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -23,11 +23,13 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dump.LogcatEchoTrackerAlways
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -42,6 +44,7 @@
 class QSTileLoggerTest : SysuiTestCase() {
 
     @Mock private lateinit var statusBarController: StatusBarStateController
+    @Mock private lateinit var logBufferFactory: LogBufferFactory
 
     private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
     private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
@@ -51,10 +54,11 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
         underTest =
             QSTileLogger(
                 mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
-                { logBuffer },
+                logBufferFactory,
                 statusBarController
             )
     }
@@ -139,7 +143,6 @@
     fun testLogStateUpdate() {
         underTest.logStateUpdate(
             TileSpec.create("test_spec"),
-            StateUpdateTrigger.ForceUpdate,
             QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
             "test_data",
         )
@@ -147,21 +150,36 @@
         assertThat(logBuffer.getStringBuffer())
             .contains(
                 "tile state update: " +
-                    "trigger=force, " +
-                    "state=[" +
-                    "label=, " +
+                    "state=[label=, " +
                     "state=INACTIVE, " +
                     "s_label=null, " +
                     "cd=null, " +
                     "sd=null, " +
                     "svi=None, " +
                     "enabled=ENABLED, " +
-                    "a11y=null" +
-                    "], " +
+                    "a11y=null], " +
                     "data=test_data"
             )
     }
 
+    @Test
+    fun testLogForceUpdate() {
+        underTest.logForceUpdate(
+            TileSpec.create("test_spec"),
+        )
+
+        assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
+    }
+
+    @Test
+    fun testLogInitialUpdate() {
+        underTest.logInitialRequest(
+            TileSpec.create("test_spec"),
+        )
+
+        assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
+    }
+
     private fun LogBuffer.getStringBuffer(): String {
         val stringWriter = StringWriter()
         dump(PrintWriter(stringWriter), 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
index fc2b7a64..479e62d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
@@ -47,11 +47,12 @@
 
     @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter
     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var logger: BluetoothTileDialogLogger
 
     @Before
     fun setUp() {
         bluetoothStateInteractor =
-            BluetoothStateInteractor(localBluetoothManager, testScope.backgroundScope)
+            BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope)
         `when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
     }
 
@@ -80,6 +81,8 @@
 
             bluetoothStateInteractor.isBluetoothEnabled = true
             verify(bluetoothAdapter).enable()
+            verify(logger)
+                .logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString())
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
index 8b66040..3b6bfee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -60,8 +61,12 @@
 
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
+    @Mock private lateinit var logger: BluetoothTileDialogLogger
+
     private val subtitleResId = R.string.quick_settings_bluetooth_tile_subtitle
 
+    private val fakeSystemClock = FakeSystemClock()
+
     private lateinit var icon: Pair<Drawable, String>
     private lateinit var bluetoothTileDialog: BluetoothTileDialog
     private lateinit var deviceItem: DeviceItem
@@ -73,7 +78,9 @@
                 ENABLED,
                 subtitleResId,
                 bluetoothTileDialogCallback,
+                fakeSystemClock,
                 uiEventLogger,
+                logger,
                 mContext
             )
         icon = Pair(drawable, DEVICE_NAME)
@@ -109,7 +116,9 @@
                 ENABLED,
                 subtitleResId,
                 bluetoothTileDialogCallback,
+                fakeSystemClock,
                 uiEventLogger,
+                logger,
                 mContext
             )
         bluetoothTileDialog.show()
@@ -138,7 +147,9 @@
                     ENABLED,
                     subtitleResId,
                     bluetoothTileDialogCallback,
+                    fakeSystemClock,
                     uiEventLogger,
+                    logger,
                     mContext
                 )
                 .Adapter(bluetoothTileDialogCallback)
@@ -162,7 +173,9 @@
                     ENABLED,
                     subtitleResId,
                     bluetoothTileDialogCallback,
+                    fakeSystemClock,
                     uiEventLogger,
+                    logger,
                     mContext
                 )
                 .Adapter(bluetoothTileDialogCallback)
@@ -182,7 +195,9 @@
                 ENABLED,
                 subtitleResId,
                 bluetoothTileDialogCallback,
+                fakeSystemClock,
                 uiEventLogger,
+                logger,
                 mContext
             )
         bluetoothTileDialog.show()
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 a0ff2ab..fb5dd21 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
@@ -32,6 +32,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -76,6 +77,8 @@
 
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
+    @Mock private lateinit var logger: BluetoothTileDialogLogger
+
     private lateinit var scheduler: TestCoroutineScheduler
     private lateinit var dispatcher: CoroutineDispatcher
     private lateinit var testScope: TestScope
@@ -91,12 +94,13 @@
                 bluetoothStateInteractor,
                 dialogLaunchAnimator,
                 activityStarter,
+                fakeSystemClock,
                 uiEventLogger,
+                logger,
                 testScope.backgroundScope,
                 dispatcher,
             )
-        `when`(deviceItemInteractor.deviceItemUpdate)
-            .thenReturn(MutableStateFlow(null).asStateFlow())
+        `when`(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
         `when`(bluetoothStateInteractor.bluetoothStateUpdate)
             .thenReturn(MutableStateFlow(null).asStateFlow())
         `when`(deviceItemInteractor.deviceItemUpdateRequest)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index 3593075..4c173cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -27,16 +27,19 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 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.Mock
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
@@ -70,6 +73,10 @@
 
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
+    @Mock private lateinit var logger: BluetoothTileDialogLogger
+
+    private val fakeSystemClock = FakeSystemClock()
+
     private lateinit var interactor: DeviceItemInteractor
 
     private lateinit var dispatcher: CoroutineDispatcher
@@ -78,7 +85,7 @@
 
     @Before
     fun setUp() {
-        dispatcher = StandardTestDispatcher()
+        dispatcher = UnconfinedTestDispatcher()
         testScope = TestScope(dispatcher)
         interactor =
             DeviceItemInteractor(
@@ -86,13 +93,16 @@
                 audioManager,
                 adapter,
                 localBluetoothManager,
+                fakeSystemClock,
                 uiEventLogger,
+                logger,
                 testScope.backgroundScope,
                 dispatcher
             )
 
         `when`(deviceItem1.cachedBluetoothDevice).thenReturn(cachedDevice1)
         `when`(deviceItem2.cachedBluetoothDevice).thenReturn(cachedDevice2)
+        `when`(cachedDevice1.address).thenReturn("ADDRESS")
         `when`(cachedDevice1.device).thenReturn(device1)
         `when`(cachedDevice2.device).thenReturn(device2)
         `when`(bluetoothTileDialogRepository.cachedDevices)
@@ -107,9 +117,10 @@
                 listOf(createFactory({ true }, deviceItem1))
             )
 
-            interactor.updateDeviceItems(mContext)
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
-            assertThat(interactor.deviceItemUpdate.value).isEmpty()
+            assertThat(latest).isEqualTo(emptyList<DeviceItem>())
         }
     }
 
@@ -121,9 +132,10 @@
                 listOf(createFactory({ false }, deviceItem1))
             )
 
-            interactor.updateDeviceItems(mContext)
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
-            assertThat(interactor.deviceItemUpdate.value).isEmpty()
+            assertThat(latest).isEqualTo(emptyList<DeviceItem>())
         }
     }
 
@@ -135,10 +147,10 @@
                 listOf(createFactory({ true }, deviceItem1))
             )
 
-            interactor.updateDeviceItems(mContext)
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
-            assertThat(interactor.deviceItemUpdate.value).hasSize(1)
-            assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem1)
+            assertThat(latest).isEqualTo(listOf(deviceItem1))
         }
     }
 
@@ -150,11 +162,10 @@
                 listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2))
             )
 
-            interactor.updateDeviceItems(mContext)
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
-            assertThat(interactor.deviceItemUpdate.value).hasSize(2)
-            assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem2)
-            assertThat(interactor.deviceItemUpdate.value!![1]).isEqualTo(deviceItem2)
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2))
         }
     }
 
@@ -177,10 +188,10 @@
             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
             `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
 
-            interactor.updateDeviceItems(mContext)
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
-            assertThat(interactor.deviceItemUpdate.value)
-                .isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
         }
     }
 
@@ -200,13 +211,56 @@
             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
             `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
 
-            interactor.updateDeviceItems(mContext)
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
-            assertThat(interactor.deviceItemUpdate.value)
-                .isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
         }
     }
 
+    @Test
+    fun testUpdateDeviceItemOnClick_connectedMedia_setActive() {
+        `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+
+        interactor.updateDeviceItemOnClick(deviceItem1)
+
+        verify(cachedDevice1).setActive()
+        verify(logger)
+            .logDeviceClick(cachedDevice1.address, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+    }
+
+    @Test
+    fun testUpdateDeviceItemOnClick_activeMedia_disconnect() {
+        `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+
+        interactor.updateDeviceItemOnClick(deviceItem1)
+
+        verify(cachedDevice1).disconnect()
+        verify(logger)
+            .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+    }
+
+    @Test
+    fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() {
+        `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+
+        interactor.updateDeviceItemOnClick(deviceItem1)
+
+        verify(cachedDevice1).disconnect()
+        verify(logger)
+            .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+    }
+
+    @Test
+    fun testUpdateDeviceItemOnClick_saved_connect() {
+        `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+
+        interactor.updateDeviceItemOnClick(deviceItem1)
+
+        verify(cachedDevice1).connect()
+        verify(logger).logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+    }
+
     private fun createFactory(
         isFilterMatchFunc: (CachedBluetoothDevice) -> Boolean,
         deviceItem: DeviceItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
new file mode 100644
index 0000000..682b2d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.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.tiles.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QSTileConfigProviderTest : SysuiTestCase() {
+
+    private val underTest =
+        QSTileConfigProviderImpl(
+            mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC })
+        )
+
+    @Test
+    fun providerReturnsConfig() {
+        assertThat(underTest.getConfig(VALID_SPEC.spec)).isNotNull()
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun throwsForInvalidSpec() {
+        underTest.getConfig(INVALID_SPEC.spec)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun validatesSpecUponCreation() {
+        QSTileConfigProviderImpl(
+            mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC })
+        )
+    }
+
+    private companion object {
+
+        val VALID_SPEC = TileSpec.create("valid_tile_spec")
+        val INVALID_SPEC = TileSpec.create("invalid_tile_spec")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 2084aeb..9bf4a75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -16,24 +16,23 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
+import android.os.UserHandle
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import com.android.internal.logging.InstanceId
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
 import com.android.systemui.qs.tiles.base.logging.QSTileLogger
-import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -55,6 +54,7 @@
     @Mock private lateinit var qsTileLogger: QSTileLogger
     @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
 
+    private val fakeUserRepository = FakeUserRepository()
     private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
     private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
     private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
@@ -76,9 +76,6 @@
         testScope.runTest {
             assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
 
-            underTest.onLifecycle(QSTileLifecycle.ALIVE)
-            underTest.onUserIdChanged(1)
-
             assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
 
             underTest.state.launchIn(backgroundScope)
@@ -86,37 +83,35 @@
 
             assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
             assertThat(fakeQSTileDataInteractor.dataRequests.first())
-                .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest))
+                .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0)))
         }
 
     private fun createViewModel(
         scope: TestScope,
         config: QSTileConfig = TEST_QS_TILE_CONFIG,
     ): QSTileViewModel =
-        BaseQSTileViewModel(
+        QSTileViewModelImpl(
             config,
-            fakeQSTileUserActionInteractor,
-            fakeQSTileDataInteractor,
-            object : QSTileDataToStateMapper<Any> {
-                override fun map(config: QSTileConfig, data: Any): QSTileState =
-                    QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+            { fakeQSTileUserActionInteractor },
+            { fakeQSTileDataInteractor },
+            {
+                object : QSTileDataToStateMapper<Any> {
+                    override fun map(config: QSTileConfig, data: Any): QSTileState =
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+                }
             },
             fakeDisabledByPolicyInteractor,
+            fakeUserRepository,
             fakeFalsingManager,
             qsTileAnalytics,
             qsTileLogger,
+            FakeSystemClock(),
             testCoroutineDispatcher,
             scope.backgroundScope,
         )
 
     private companion object {
 
-        val TEST_QS_TILE_CONFIG =
-            QSTileConfig(
-                TileSpec.create("default"),
-                Icon.Resource(0, null),
-                0,
-                InstanceId.fakeInstanceId(0),
-            )
+        val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {}
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 58e36be..10c7c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 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.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -49,6 +51,7 @@
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -61,6 +64,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
index 421c355..fe80f70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -21,7 +21,7 @@
 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.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -36,7 +36,7 @@
 @RunWith(AndroidTestingRunner::class)
 class RetailModeSettingsRepositoryTest : SysuiTestCase() {
 
-    private val globalSettings = FakeSettings()
+    private val globalSettings = FakeGlobalSettings()
 
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 2e16577..88a5c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.model.SysUiState
@@ -52,7 +54,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -138,6 +139,7 @@
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -150,6 +152,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
@@ -463,8 +466,7 @@
                 fromScene = getCurrentSceneInUi(),
                 toScene = to.key,
                 progress = progressFlow,
-                isInitiatedByUserInput = false,
-                isUserInputOngoing = flowOf(false),
+                isUserInputDriven = false,
             )
         runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 740c6d9..432bd0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -29,7 +29,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -120,8 +119,7 @@
                     fromScene = SceneKey.Lockscreen,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 31d26c0..8b23d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -83,8 +83,7 @@
                     fromScene = SceneKey.Lockscreen,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
@@ -122,8 +121,7 @@
                     fromScene = underTest.desiredScene.value.key,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(transitionTo).isEqualTo(SceneKey.Shade)
 
@@ -160,8 +158,7 @@
                         fromScene = SceneKey.Gone,
                         toScene = SceneKey.Lockscreen,
                         progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             val transitioning by
@@ -180,8 +177,7 @@
                         fromScene = SceneKey.Shade,
                         toScene = SceneKey.QuickSettings,
                         progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             underTest.setTransitionState(transitionState)
@@ -198,8 +194,7 @@
                         fromScene = SceneKey.Shade,
                         toScene = SceneKey.Lockscreen,
                         progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             val transitioning by
@@ -227,8 +222,7 @@
                     fromScene = SceneKey.Shade,
                     toScene = SceneKey.Lockscreen,
                     progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(transitioning).isTrue()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 3b9621e..c1f2d0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -22,11 +22,11 @@
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.Flags
 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
@@ -42,9 +42,10 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Assume.assumeTrue
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.clearInvocations
@@ -86,6 +87,11 @@
             powerInteractor = powerInteractor,
         )
 
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
+    }
+
     @Test
     fun hydrateVisibility() =
         testScope.runTest {
@@ -109,8 +115,7 @@
                     fromScene = SceneKey.Gone,
                     toScene = SceneKey.Shade,
                     progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(isVisible).isTrue()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
@@ -123,8 +128,7 @@
                     fromScene = SceneKey.Shade,
                     toScene = SceneKey.Gone,
                     progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(isVisible).isTrue()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -294,6 +298,7 @@
                 initialSceneKey = SceneKey.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
+                startsAwake = false
             )
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
@@ -301,6 +306,7 @@
             utils.deviceEntryRepository.setUnlocked(true)
             runCurrent()
             powerInteractor.setAwakeForTest()
+            runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -405,42 +411,43 @@
                 initialSceneKey = SceneKey.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
+                startsAwake = false,
             )
             underTest.start()
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
+            verify(falsingCollector, times(1)).onScreenOff()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
+            verify(falsingCollector, times(1)).onScreenOff()
 
             powerInteractor.setAsleepForTest()
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, times(1)).onScreenOff()
+            verify(falsingCollector, times(2)).onScreenOff()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
-            verify(falsingCollector, times(1)).onScreenOff()
+            verify(falsingCollector, times(2)).onScreenOff()
 
             powerInteractor.setAsleepForTest()
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
-            verify(falsingCollector, times(2)).onScreenOff()
+            verify(falsingCollector, times(3)).onScreenOff()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, times(2)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
-            verify(falsingCollector, times(2)).onScreenOff()
+            verify(falsingCollector, times(3)).onScreenOff()
         }
 
     @Test
@@ -511,13 +518,13 @@
             verify(falsingCollector, times(2)).onBouncerHidden()
         }
 
-    private fun prepareState(
+    private fun TestScope.prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
         initialSceneKey: SceneKey? = null,
         authenticationMethod: AuthenticationMethodModel? = null,
+        startsAwake: Boolean = true,
     ): MutableStateFlow<ObservableTransitionState> {
-        assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
         sceneContainerFlags.enabled = true
         utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
         utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
@@ -539,6 +546,13 @@
                 authenticationMethod != AuthenticationMethodModel.None
             )
         }
+        if (startsAwake) {
+            powerInteractor.setAwakeForTest()
+        } else {
+            powerInteractor.setAsleepForTest()
+        }
+        runCurrent()
+
         return transitionStateFlow
     }
 }
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 f6195aa..0bed4d0 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,7 +16,10 @@
 
 package com.android.systemui.scene.shared.flag
 
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.filters.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
+import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -24,8 +27,8 @@
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -36,27 +39,50 @@
     private val testCase: TestCase,
 ) : SysuiTestCase() {
 
+    @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
     private lateinit var underTest: SceneContainerFlags
 
     @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)
+
         val featureFlags =
             FakeFeatureFlagsClassic().apply {
-                SceneContainerFlagsImpl.flags.forEach { flag ->
-                    when (flag) {
-                        is ResourceBooleanFlag -> set(flag, testCase.areAllFlagsSet)
-                        is ReleasedFlag -> set(flag, testCase.areAllFlagsSet)
-                        is UnreleasedFlag -> set(flag, testCase.areAllFlagsSet)
-                        else -> error("Unsupported flag type ${flag.javaClass}")
+                SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
+                    when (flagToken) {
+                        is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
+                        is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
+                        is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
+                        else -> error("Unsupported flag type ${flagToken.javaClass}")
                     }
                 }
             }
-        underTest = SceneContainerFlagsImpl(featureFlags, testCase.isComposeAvailable)
+        // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
+        val aconfigFlags = FakeFeatureFlagsImpl()
+
+        listOf(
+                AconfigFlags.FLAG_SCENE_CONTAINER,
+            )
+            .forEach { flagToken ->
+                setFlagsRule.enableFlags(flagToken)
+                aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
+            }
+
+        underTest =
+            SceneContainerFlagsImpl(
+                featureFlagsClassic = featureFlags,
+                featureFlags = aconfigFlags,
+                isComposeAvailable = testCase.isComposeAvailable,
+            )
     }
 
     @Test
     fun isEnabled() {
-        assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
         assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6e6833d..49049bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -16,19 +16,25 @@
 
 package com.android.systemui.screenrecord;
 
+import static android.os.Process.myUid;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Dialog;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
-import android.os.Looper;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
@@ -37,6 +43,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.plugins.ActivityStarter;
@@ -54,12 +62,15 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 /**
  * Tests for exception handling and  bitmap configuration in adding smart actions to Screenshot
  * Notification.
  */
 public class RecordingControllerTest extends SysuiTestCase {
 
+    private static final int TEST_USER_ID = 12345;
+
     private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     @Mock
@@ -76,6 +87,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
@@ -85,9 +98,21 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        Context spiedContext = spy(mContext);
+        when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
+
+        when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
+
         mFeatureFlags = new FakeFeatureFlags();
-        mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
-                mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
+        mController = new RecordingController(
+                mMainExecutor,
+                mBroadcastDispatcher,
+                mContext,
+                mFeatureFlags,
+                mUserContextProvider,
+                () -> mDevicePolicyResolver,
+                mUserTracker,
+                mMediaProjectionMetricsLogger);
         mController.addCallback(mCallback);
     }
 
@@ -95,10 +120,6 @@
     // starting, and notifies listeners.
     @Test
     public void testCancelCountdown() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         mController.startCountdown(100, 10, null, null);
 
         assertTrue(mController.isStarting());
@@ -115,10 +136,6 @@
     // Test that when recording is started, the start intent is sent and listeners are notified.
     @Test
     public void testStartRecording() throws PendingIntent.CanceledException {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         PendingIntent startIntent = Mockito.mock(PendingIntent.class);
         mController.startCountdown(0, 0, startIntent, null);
 
@@ -129,10 +146,6 @@
     // Test that when recording is stopped, the stop intent is sent and listeners are notified.
     @Test
     public void testStopRecording() throws PendingIntent.CanceledException {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         PendingIntent startIntent = Mockito.mock(PendingIntent.class);
         PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
 
@@ -160,10 +173,6 @@
     // Test that broadcast will update state
     @Test
     public void testUpdateStateBroadcast() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         // When a recording has started
         PendingIntent startIntent = Mockito.mock(PendingIntent.class);
         mController.startCountdown(0, 0, startIntent, null);
@@ -189,10 +198,6 @@
     // Test that switching users will stop an ongoing recording
     @Test
     public void testUserChange() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         // If we are recording
         PendingIntent startIntent = Mockito.mock(PendingIntent.class);
         PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
@@ -209,10 +214,6 @@
 
     @Test
     public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -225,10 +226,6 @@
 
     @Test
     public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
 
@@ -240,10 +237,6 @@
 
     @Test
     public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -256,10 +249,6 @@
 
     @Test
     public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -269,4 +258,19 @@
 
         assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
     }
+
+    @Test
+    public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+        mController.createScreenRecordDialog(mContext, mFeatureFlags,
+                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+        verify(mMediaProjectionMetricsLogger)
+                .notifyProjectionInitiated(
+                        /* hostUid= */ myUid(),
+                        SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 3ae1f35..fd38139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenrecord
 
+import android.content.Intent
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -25,18 +26,24 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
 import com.android.systemui.mediaprojection.permission.SINGLE_APP
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -50,6 +57,7 @@
     @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var flags: FeatureFlags
     @Mock private lateinit var onStartRecordingClicked: Runnable
+    @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
 
     private lateinit var dialog: ScreenRecordPermissionDialog
 
@@ -61,10 +69,12 @@
             ScreenRecordPermissionDialog(
                 context,
                 UserHandle.of(0),
+                TEST_HOST_UID,
                 controller,
                 starter,
                 userContextProvider,
-                onStartRecordingClicked
+                onStartRecordingClicked,
+                mediaProjectionMetricsLogger,
             )
         dialog.onCreate(null)
         whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
@@ -104,6 +114,19 @@
     }
 
     @Test
+    fun startClicked_singleAppSelected_passesHostUidToAppSelector() {
+        dialog.show()
+        onSpinnerItemSelected(SINGLE_APP)
+
+        clickOnStart()
+
+        assertExtraPassedToAppSelector(
+            extraKey = MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID,
+            value = TEST_HOST_UID
+        )
+    }
+
+    @Test
     fun showDialog_dialogIsShowing() {
         dialog.show()
 
@@ -111,6 +134,15 @@
     }
 
     @Test
+    fun showDialog_singleAppIsDefault() {
+        dialog.show()
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
+        assertEquals(spinner.adapter.getItem(0), singleApp)
+    }
+
+    @Test
     fun showDialog_cancelClicked_dialogIsDismissed() {
         dialog.show()
 
@@ -119,13 +151,51 @@
         assertThat(dialog.isShowing).isFalse()
     }
 
+    @Test
+    fun showDialog_cancelClickedMultipleTimes_projectionRequestCancelledIsLoggedOnce() {
+        dialog.show()
+
+        clickOnCancel()
+        clickOnCancel()
+
+        verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
+    }
+
+    @Test
+    fun dismissDialog_dismissCalledMultipleTimes_projectionRequestCancelledIsLoggedOnce() {
+        dialog.show()
+
+        TestableLooper.get(this).runWithLooper {
+            dialog.dismiss()
+            dialog.dismiss()
+        }
+
+        verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
+    }
+
     private fun clickOnCancel() {
         dialog.requireViewById<View>(android.R.id.button2).performClick()
     }
 
+    private fun clickOnStart() {
+        dialog.requireViewById<View>(android.R.id.button1).performClick()
+    }
+
     private fun onSpinnerItemSelected(position: Int) {
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
         checkNotNull(spinner.onItemSelectedListener)
             .onItemSelected(spinner, mock(), position, /* id= */ 0)
     }
+
+    private fun assertExtraPassedToAppSelector(extraKey: String, value: Int) {
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(starter).startActivity(intentCaptor.capture(), /* dismissShade= */ eq(true))
+
+        val intent = intentCaptor.value
+        assertThat(intent.extras!!.getInt(extraKey)).isEqualTo(value)
+    }
+
+    companion object {
+        private const val TEST_HOST_UID = 12345
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 0d694ee..7e41745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -47,7 +46,6 @@
 
     private val scope = CoroutineScope(Dispatchers.Unconfined)
     private val policy = FakeScreenshotPolicy()
-    private val flags = FakeFeatureFlags()
 
     /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
     @Test
@@ -58,7 +56,7 @@
                     .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
                     .build()
             )
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         var result: ScreenshotData? = null
         var callbackCount = 0
@@ -86,7 +84,7 @@
 
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         val processedData = processor.process(ScreenshotData.fromRequest(request))
 
@@ -111,7 +109,7 @@
 
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         val processedData = processor.process(ScreenshotData.fromRequest(request))
 
@@ -138,7 +136,7 @@
 
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         Assert.assertThrows(IllegalStateException::class.java) {
             runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -148,7 +146,7 @@
     @Test
     fun testProvidedImageScreenshot() = runBlocking {
         val bounds = Rect(50, 50, 150, 150)
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         policy.setManagedProfile(USER_ID, false)
 
@@ -173,7 +171,7 @@
     @Test
     fun testProvidedImageScreenshot_managedProfile() = runBlocking {
         val bounds = Rect(50, 50, 150, 150)
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         // Indicate that the screenshot belongs to a manged profile
         policy.setManagedProfile(USER_ID, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index a105c15..3dc9037 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
@@ -43,8 +44,11 @@
 
     private val controller0 = mock<ScreenshotController>()
     private val controller1 = mock<ScreenshotController>()
+    private val notificationsController0 = mock<ScreenshotNotificationsController>()
+    private val notificationsController1 = mock<ScreenshotNotificationsController>()
     private val controllerFactory = mock<ScreenshotController.Factory>()
     private val callback = mock<TakeScreenshotService.RequestCallback>()
+    private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
 
     private val fakeDisplayRepository = FakeDisplayRepository()
     private val requestProcessor = FakeRequestProcessor()
@@ -59,12 +63,15 @@
             testScope,
             requestProcessor,
             eventLogger,
+            notificationControllerFactory
         )
 
     @Before
     fun setUp() {
-        whenever(controllerFactory.create(eq(0))).thenReturn(controller0)
-        whenever(controllerFactory.create(eq(1))).thenReturn(controller1)
+        whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)
+        whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1)
+        whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
+        whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
     }
 
     @Test
@@ -74,8 +81,8 @@
             val onSaved = { _: Uri -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
-            verify(controllerFactory).create(eq(0))
-            verify(controllerFactory).create(eq(1))
+            verify(controllerFactory).create(eq(0), any())
+            verify(controllerFactory).create(eq(1), any())
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -107,8 +114,8 @@
                 callback
             )
 
-            verify(controllerFactory).create(eq(0))
-            verify(controllerFactory, never()).create(eq(1))
+            verify(controllerFactory).create(eq(0), any())
+            verify(controllerFactory, never()).create(eq(1), any())
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -139,7 +146,7 @@
     @Test
     fun executeScreenshots_allowedTypes_allCaptured() =
         testScope.runTest {
-            whenever(controllerFactory.create(any())).thenReturn(controller0)
+            whenever(controllerFactory.create(any(), any())).thenReturn(controller0)
 
             setDisplays(
                 display(TYPE_INTERNAL, id = 0),
@@ -310,6 +317,123 @@
             screenshotExecutor.onDestroy()
         }
 
+    @Test
+    fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromProcessor_logsUiError() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            verify(notificationsController0).notifyScreenshotError(any())
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            verify(notificationsController0).notifyScreenshotError(any())
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            whenever(controller0.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+            whenever(controller1.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromScreenshotController_reportsError() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            whenever(controller0.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+            whenever(controller1.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            whenever(controller0.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+            whenever(controller1.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            verify(notificationsController0).notifyScreenshotError(any())
+            verify(notificationsController1).notifyScreenshotError(any())
+            screenshotExecutor.onDestroy()
+        }
+
     private suspend fun TestScope.setDisplays(vararg displays: Display) {
         fakeDisplayRepository.emit(displays.toSet())
         runCurrent()
@@ -328,8 +452,9 @@
     private class FakeRequestProcessor : ScreenshotRequestProcessor {
         var processed: ScreenshotData? = null
         var toReturn: ScreenshotData? = null
-
+        var shouldThrowException = false
         override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+            if (shouldThrowException) throw RequestProcessorException("")
             processed = screenshot
             return toReturn ?: screenshot
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 6205d90..f3809aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -65,6 +65,7 @@
     private val requestProcessor = mock<RequestProcessor>()
     private val devicePolicyManager = mock<DevicePolicyManager>()
     private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+    private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
     private val notificationsController = mock<ScreenshotNotificationsController>()
     private val callback = mock<RequestCallback>()
 
@@ -86,7 +87,8 @@
             )
             .thenReturn(false)
         whenever(userManager.isUserUnlocked).thenReturn(true)
-        whenever(controllerFactory.create(any())).thenReturn(controller)
+        whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+        whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
 
         // Stub request processor as a synchronous no-op for tests with the flag enabled
         doAnswer {
@@ -323,7 +325,7 @@
                 userManager,
                 devicePolicyManager,
                 eventLogger,
-                notificationsController,
+                notificationsControllerFactory,
                 mContext,
                 Runnable::run,
                 flags,
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 8d8c70e..2ce4b04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -84,6 +84,7 @@
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
@@ -120,6 +121,8 @@
 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.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -137,6 +140,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -157,6 +161,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -167,6 +172,7 @@
 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;
@@ -175,8 +181,10 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 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.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
@@ -196,6 +204,8 @@
 import java.util.Optional;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.StateFlowKt;
+import kotlinx.coroutines.test.TestScope;
 
 public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
 
@@ -323,16 +333,19 @@
             mEmptySpaceClickListenerCaptor;
     @Mock protected ActivityStarter mActivityStarter;
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
-    @Mock private ShadeInteractor mShadeInteractor;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
     @Mock private KeyguardRootView mKeyguardRootView;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+    @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
+    protected SceneTestUtils mUtils = new SceneTestUtils(this);
+    protected TestScope mTestScope = mUtils.getTestScope();
+    protected ShadeInteractor mShadeInteractor;
     protected PowerInteractor mPowerInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
@@ -368,10 +381,31 @@
         mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
         mShadeRepository = new FakeShadeRepository();
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
+        when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
+                StateFlowKt.MutableStateFlow(false));
+        mShadeInteractor = new ShadeInteractor(
+                mTestScope.getBackgroundScope(),
+                new FakeDeviceProvisioningRepository(),
+                new FakeDisableFlagsRepository(),
+                mDozeParameters,
+                new FakeSceneContainerFlags(),
+                mUtils::sceneInteractor,
+                mFakeKeyguardRepository,
+                mKeyguardTransitionInteractor,
+                mPowerInteractor,
+                new FakeUserSetupRepository(),
+                mock(UserSwitcherInteractor.class),
+                new SharedNotificationContainerInteractor(
+                        new FakeConfigurationRepository(),
+                        mContext,
+                        new ResourcesSplitShadeStateController()
+                ),
+                mShadeRepository
+        );
 
         SystemClock systemClock = new FakeSystemClock();
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
-                mInteractionJankMonitor, mShadeExpansionStateManager);
+        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
+                mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor);
 
         KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
         keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -388,6 +422,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 mPowerInteractor));
 
@@ -529,8 +564,9 @@
                 new NotificationWakeUpCoordinator(
                         mDumpManager,
                         mock(HeadsUpManager.class),
-                        new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
-                                mInteractionJankMonitor, mShadeExpansionStateManager),
+                        new StatusBarStateControllerImpl(new UiEventLoggerFake(),
+                                mInteractionJankMonitor,
+                                mJavaAdapter, () -> mShadeInteractor),
                         mKeyguardBypassController,
                         mDozeParameters,
                         mScreenOffAnimationController,
@@ -667,7 +703,8 @@
                 mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
-                mPowerInteractor);
+                mPowerInteractor,
+                mKeyguardClockPositionAlgorithm);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
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 eb00610..4ba850c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -54,7 +54,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -90,7 +89,8 @@
 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.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 
 import com.google.common.util.concurrent.MoreExecutors;
 
@@ -131,6 +131,7 @@
     @Mock private AuthController mAuthController;
     @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private ShadeWindowLogger mShadeWindowLogger;
+    @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
@@ -183,7 +184,6 @@
                 powerInteractor,
                 featureFlags,
                 sceneContainerFlags,
-                new FakeDeviceEntryRepository(),
                 new FakeKeyguardBouncerRepository(),
                 configurationRepository,
                 shadeRepository,
@@ -216,6 +216,7 @@
                 keyguardInteractor,
                 featureFlags,
                 mKeyguardSecurityModel,
+                mSelectedUserInteractor,
                 powerInteractor);
 
         mShadeInteractor =
@@ -230,7 +231,7 @@
                         keyguardTransitionInteractor,
                         powerInteractor,
                         new FakeUserSetupRepository(),
-                        mock(UserInteractor.class),
+                        mock(UserSwitcherInteractor.class),
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
@@ -255,7 +256,8 @@
                 mAuthController,
                 mShadeExpansionStateManager,
                 () -> mShadeInteractor,
-                mShadeWindowLogger) {
+                mShadeWindowLogger,
+                () -> mSelectedUserInteractor) {
                     @Override
                     protected boolean isDebuggable() {
                         return 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 b4f9e8d..4e3e165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,6 +21,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.KeyEvent
 import android.view.MotionEvent
+import android.view.View
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardMessageAreaController
@@ -43,13 +44,22 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.flags.Flags.MIGRATE_NSSL
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+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.KeyEventInteractor
+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
@@ -69,7 +79,8 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -81,7 +92,9 @@
 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.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -96,6 +109,7 @@
 import org.mockito.Mockito.anyFloat
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.Optional
@@ -134,6 +148,8 @@
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+    private lateinit var mCommunalRepository: FakeCommunalRepository
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -142,12 +158,15 @@
         Optional<UnfoldTransitionProgressProvider>
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var dragDownHelper: DragDownHelper
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock
     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
-    @Mock lateinit var keyEventInteractor: KeyEventInteractor
+    @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    private val notificationExpansionRepository = NotificationExpansionRepository()
+    private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository()
+    private val notificationLaunchAnimationInteractor =
+            NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
 
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -157,7 +176,7 @@
 
     private lateinit var testScope: TestScope
 
-    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
 
     @Before
     fun setUp() {
@@ -172,14 +191,16 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
 
-        featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
-        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
-        featureFlags.set(Flags.MIGRATE_NSSL, false)
-        featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
+        featureFlagsClassic = FakeFeatureFlagsClassic()
+        featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
+        featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
+        featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+        featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+        featureFlagsClassic.set(MIGRATE_NSSL, false)
+        featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
+
+        mCommunalRepository = FakeCommunalRepository()
 
         testScope = TestScope()
         fakeClock = FakeSystemClock()
@@ -200,8 +221,6 @@
                 centralSurfaces,
                 dozeServiceHost,
                 dozeScrimController,
-                backActionInteractor,
-                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
@@ -216,14 +235,16 @@
                 mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
-                notificationExpansionRepository,
-                featureFlags,
+                mCommunalViewModel,
+                mCommunalRepository,
+                notificationLaunchAnimationInteractor,
+                featureFlagsClassic,
                 fakeClock,
                 BouncerMessageInteractor(
                     repository = BouncerMessageRepositoryImpl(),
                     userRepository = FakeUserRepository(),
                     countDownTimerUtil = mock(CountDownTimerUtil::class.java),
-                    featureFlags = featureFlags,
+                    featureFlags = featureFlagsClassic,
                     updateMonitor = mock(KeyguardUpdateMonitor::class.java),
                     biometricSettingsRepository = FakeBiometricSettingsRepository(),
                     applicationScope = testScope.backgroundScope,
@@ -243,6 +264,7 @@
                             mock(KeyguardUpdateMonitor::class.java),
                             FakeTrustRepository(),
                             testScope.backgroundScope,
+                            mSelectedUserInteractor,
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
@@ -251,9 +273,10 @@
                     securityModel = mock(KeyguardSecurityModel::class.java),
                 ),
                 BouncerLogger(logcatLogBuffer("BouncerLog")),
-                keyEventInteractor,
+                sysUIKeyEventHandler,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
+                mSelectedUserInteractor,
             )
         underTest.setupExpandedStatusBar()
         underTest.setDragDownHelper(dragDownHelper)
@@ -441,7 +464,7 @@
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
                 .thenReturn(true)
 
-        featureFlags.set(Flags.MIGRATE_NSSL, true)
+        featureFlagsClassic.set(MIGRATE_NSSL, true)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -458,7 +481,7 @@
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
                 .thenReturn(false)
 
-        featureFlags.set(Flags.MIGRATE_NSSL, true)
+        featureFlagsClassic.set(MIGRATE_NSSL, true)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -472,24 +495,66 @@
         }
 
     @Test
+    fun setsUpCommunalHubLayout_whenFlagEnabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        mCommunalRepository.setIsCommunalEnabled(true)
+
+        val mockCommunalPlaceholder = mock(View::class.java)
+        val fakeViewIndex = 20
+        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+        whenever(view.context).thenReturn(context)
+
+        underTest.setupCommunalHubLayout()
+
+        // Communal view added as a child of the container at the proper index, the stub is removed.
+        verify(view).removeView(mockCommunalPlaceholder)
+        verify(view).addView(any(), eq(fakeViewIndex))
+    }
+
+    @Test
+    fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        mCommunalRepository.setIsCommunalEnabled(false)
+
+        val mockCommunalPlaceholder = mock(View::class.java)
+        val fakeViewIndex = 20
+        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+        whenever(view.context).thenReturn(context)
+
+        underTest.setupCommunalHubLayout()
+
+        // No adding or removing of views occurs.
+        verify(view, times(0)).removeView(mockCommunalPlaceholder)
+        verify(view, times(0)).addView(any(), eq(fakeViewIndex))
+    }
+
+    @Test
     fun forwardsDispatchKeyEvent() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
         interactionEventHandler.dispatchKeyEvent(keyEvent)
-        verify(keyEventInteractor).dispatchKeyEvent(keyEvent)
+        verify(sysUIKeyEventHandler).dispatchKeyEvent(keyEvent)
     }
 
     @Test
     fun forwardsDispatchKeyEventPreIme() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
         interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
-        verify(keyEventInteractor).dispatchKeyEventPreIme(keyEvent)
+        verify(sysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent)
     }
 
     @Test
     fun forwardsInterceptMediaKey() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
         interactionEventHandler.interceptMediaKey(keyEvent)
-        verify(keyEventInteractor).interceptMediaKey(keyEvent)
+        verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
     }
 
     companion object {
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 189c9e2..3d5d26a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,13 +42,15 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 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.KeyEventInteractor
+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
@@ -67,7 +69,8 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -79,6 +82,7 @@
 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.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -141,9 +145,12 @@
     private lateinit var unfoldTransitionProgressProvider:
         Optional<UnfoldTransitionProgressProvider>
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+    private lateinit var mCommunalRepository: FakeCommunalRepository
     @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
@@ -176,6 +183,8 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow())
 
+        mCommunalRepository = FakeCommunalRepository()
+
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
@@ -202,8 +211,6 @@
                 centralSurfaces,
                 dozeServiceHost,
                 dozeScrimController,
-                backActionInteractor,
-                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
@@ -218,7 +225,9 @@
                 Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
-                NotificationExpansionRepository(),
+                mCommunalViewModel,
+                mCommunalRepository,
+                NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
                 featureFlags,
                 FakeSystemClock(),
                 BouncerMessageInteractor(
@@ -245,6 +254,7 @@
                             Mockito.mock(KeyguardUpdateMonitor::class.java),
                             FakeTrustRepository(),
                             testScope.backgroundScope,
+                            mSelectedUserInteractor,
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
@@ -253,9 +263,10 @@
                     securityModel = Mockito.mock(KeyguardSecurityModel::class.java),
                 ),
                 BouncerLogger(logcatLogBuffer("BouncerLog")),
-                Mockito.mock(KeyEventInteractor::class.java),
+                Mockito.mock(SysUIKeyEventHandler::class.java),
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
+                mSelectedUserInteractor,
             )
 
         controller.setupExpandedStatusBar()
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 65174ba..2f45b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -39,7 +39,6 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.flags.FeatureFlags;
@@ -95,15 +94,17 @@
 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.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
+import dagger.Lazy;
+
 import org.junit.After;
 import org.junit.Before;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
 import kotlinx.coroutines.test.TestScope;
 
 public class QuickSettingsControllerBaseTest extends SysuiTestCase {
@@ -162,7 +163,8 @@
     @Mock protected DumpManager mDumpManager;
     @Mock protected UiEventLogger mUiEventLogger;
     @Mock protected CastController mCastController;
-    @Mock protected UserInteractor mUserInteractor;
+    @Mock protected UserSwitcherInteractor mUserSwitcherInteractor;
+    @Mock protected SelectedUserInteractor mSelectedUserInteractor;
     protected FakeDisableFlagsRepository mDisableFlagsRepository =
             new FakeDisableFlagsRepository();
     protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
@@ -185,8 +187,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
-                mInteractionJankMonitor, mShadeExpansionStateManager);
+        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
+                mInteractionJankMonitor, mock(JavaAdapter.class), () -> mShadeInteractor);
 
         FakeDeviceProvisioningRepository deviceProvisioningRepository =
                 new FakeDeviceProvisioningRepository();
@@ -216,7 +218,6 @@
                 powerInteractor,
                 featureFlags,
                 sceneContainerFlags,
-                new FakeDeviceEntryRepository(),
                 new FakeKeyguardBouncerRepository(),
                 configurationRepository,
                 mShadeRepository,
@@ -249,6 +250,7 @@
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
+                mSelectedUserInteractor,
                 powerInteractor);
 
         ResourcesSplitShadeStateController splitShadeStateController =
@@ -266,7 +268,7 @@
                         keyguardTransitionInteractor,
                         powerInteractor,
                         new FakeUserSetupRepository(),
-                        mUserInteractor,
+                        mUserSwitcherInteractor,
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 5ca45f3..7cb6d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -398,6 +398,12 @@
                 .isEqualTo(mQsController.getScrimCornerRadius());
     }
 
+    @Test
+    public void disallowTouches_nullQs_false() {
+        mQsController.setQs(null);
+        assertThat(mQsController.disallowTouches()).isFalse();
+    }
+
     private void lockScreen() {
         mQsController.setBarState(KEYGUARD);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index bcb060d..3a260ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -60,7 +60,6 @@
 import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -446,105 +445,6 @@
         }
 
     @Test
-    fun expanding_shadeDraggedDown_expandingTrue() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade and QS collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade partially expanded
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun expanding_qsDraggedDown_expandingTrue() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade and QS collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade partially expanded
-            shadeRepository.setQsExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun expanding_shadeDraggedUpAndDown() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // WHEN shade starts collapsed then partially expanded
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged up a bit
-            shadeRepository.setLegacyShadeExpansion(.2f)
-            runCurrent()
-
-            // THEN anyExpanding is still true
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down a bit
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN anyExpanding is still true
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN anyExpanding is now false
-            assertThat(actual).isFalse()
-
-            // WHEN shade dragged up a bit
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN anyExpanding is still false
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun expanding_shadeDraggedDownThenUp_expandingFalse() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade starts collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade expands but doesn't complete
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpanding is false
-            assertThat(actual).isFalse()
-        }
-
-    @Test
     fun lockscreenShadeExpansion_idle_onScene() =
         testScope.runTest() {
             // GIVEN an expansion flow based on transitions to and from a scene
@@ -595,8 +495,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = key,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -633,8 +532,7 @@
                         fromScene = key,
                         toScene = SceneKey.Lockscreen,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -670,8 +568,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = SceneKey.Shade,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -947,8 +844,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = key,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -985,8 +881,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = key,
                         progress = progress,
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = true,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -1023,8 +918,7 @@
                         fromScene = key,
                         toScene = SceneKey.Lockscreen,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -1061,8 +955,7 @@
                         fromScene = key,
                         toScene = SceneKey.Lockscreen,
                         progress = progress,
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = true,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -1097,9 +990,8 @@
                     ObservableTransitionState.Transition(
                         fromScene = SceneKey.Lockscreen,
                         toScene = SceneKey.QuickSettings,
-                        progress = MutableStateFlow(0f),
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
+                        progress = progress,
+                        isUserInputDriven = true,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index bb20d94..df38f93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -4,6 +4,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 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
@@ -17,7 +19,6 @@
 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
@@ -32,6 +33,7 @@
     private val sceneInteractor = utils.sceneInteractor()
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -44,6 +46,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
@@ -85,8 +88,7 @@
                         fromScene = SceneKey.Shade,
                         toScene = SceneKey.QuickSettings,
                         progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             )
@@ -104,8 +106,7 @@
                         fromScene = SceneKey.QuickSettings,
                         toScene = SceneKey.Shade,
                         progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             )
@@ -123,8 +124,7 @@
                         fromScene = SceneKey.Gone,
                         toScene = SceneKey.Shade,
                         progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c423782..3064f4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 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.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -55,6 +57,7 @@
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -67,6 +70,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
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 8f06e63..6eabf44 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
@@ -145,22 +145,15 @@
     }
 
     @Test
-    public void testOnRepeatedlyLoadUnload_PluginFreed() {
+    public void testOnUnloadAfterLoad() {
         mPluginInstance.onCreate();
         mPluginInstance.loadPlugin();
+        assertNotNull(mPluginInstance.getPlugin());
         assertInstances(1, 1);
 
         mPluginInstance.unloadPlugin();
         assertNull(mPluginInstance.getPlugin());
         assertInstances(0, 0);
-
-        mPluginInstance.loadPlugin();
-        assertInstances(1, 1);
-
-        mPluginInstance.unloadPlugin();
-        mPluginInstance.onDestroy();
-        assertNull(mPluginInstance.getPlugin());
-        assertInstances(0, 0);
     }
 
     @Test
@@ -169,7 +162,7 @@
         mPluginInstance.onCreate();
         assertEquals(1, mPluginListener.mAttachedCount);
         assertEquals(0, mPluginListener.mLoadCount);
-        assertEquals(null, mPluginInstance.getPlugin());
+        assertNull(mPluginInstance.getPlugin());
         assertInstances(0, 0);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 2bcad1d..dd3ac92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -73,10 +73,10 @@
 
 import com.android.keyguard.TrustGrantFlags;
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.res.R;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardIndication;
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
+import com.android.systemui.res.R;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -1543,7 +1543,7 @@
 
     private void setupFingerprintUnlockPossible(boolean possible) {
         when(mKeyguardUpdateMonitor
-                .getCachedIsUnlockWithFingerprintPossible(getCurrentUser()))
+                .isUnlockWithFingerprintPossible(getCurrentUser()))
                 .thenReturn(possible);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index e8923a5..970a0f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -9,13 +9,18 @@
 import com.android.TestMocksModule
 import com.android.systemui.ExpandHelper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
 import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.plugins.qs.QS
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -26,7 +31,9 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
 import dagger.BindsInstance
 import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,6 +58,7 @@
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
@@ -64,10 +72,8 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
 
+    private lateinit var transitionController: LockscreenShadeTransitionController
     private lateinit var testComponent: TestComponent
-
-    private val transitionController
-        get() = testComponent.transitionController
     private val configurationController
         get() = testComponent.configurationController
     private val disableFlagsRepository
@@ -85,8 +91,11 @@
     @Mock lateinit var mediaHierarchyManager: MediaHierarchyManager
     @Mock lateinit var nsslController: NotificationStackScrollLayoutController
     @Mock lateinit var qS: QS
+    @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
     @Mock lateinit var scrimController: ScrimController
     @Mock lateinit var shadeViewController: ShadeViewController
+    @Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller
+    @Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller
     @Mock lateinit var stackscroller: NotificationStackScrollLayout
     @Mock lateinit var statusbarStateController: SysuiStatusBarStateController
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
@@ -135,6 +144,49 @@
                         )
                 )
 
+        transitionController =
+            LockscreenShadeTransitionController(
+                statusBarStateController = statusbarStateController,
+                logger = mock(),
+                keyguardBypassController = keyguardBypassController,
+                lockScreenUserManager = lockScreenUserManager,
+                falsingCollector = FalsingCollectorFake(),
+                ambientState = mock(),
+                mediaHierarchyManager = mediaHierarchyManager,
+                scrimTransitionController =
+                    LockscreenShadeScrimTransitionController(
+                        scrimController = scrimController,
+                        context = context,
+                        configurationController = configurationController,
+                        dumpManager = mock(),
+                        splitShadeStateController = ResourcesSplitShadeStateController()
+                    ),
+                keyguardTransitionControllerFactory = { notificationPanelController ->
+                    LockscreenShadeKeyguardTransitionController(
+                        mediaHierarchyManager = mediaHierarchyManager,
+                        notificationPanelController = notificationPanelController,
+                        context = context,
+                        configurationController = configurationController,
+                        dumpManager = mock(),
+                        splitShadeStateController = ResourcesSplitShadeStateController()
+                    )
+                },
+                depthController = depthController,
+                context = context,
+                splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
+                singleShadeOverScrollerFactory = { singleShadeOverScroller },
+                activityStarter = mock(),
+                wakefulnessLifecycle = mock(),
+                configurationController = configurationController,
+                falsingManager = FalsingManagerFake(),
+                dumpManager = mock(),
+                qsTransitionControllerFactory = { qsTransitionController },
+                shadeRepository = testComponent.shadeRepository,
+                shadeInteractor = testComponent.shadeInteractor,
+                powerInteractor = testComponent.powerInteractor,
+                splitShadeStateController = ResourcesSplitShadeStateController(),
+            )
+
         transitionController.addCallback(transitionControllerCallback)
         transitionController.shadeViewController = shadeViewController
         transitionController.centralSurfaces = centralSurfaces
@@ -259,7 +311,7 @@
         verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
         verify(transitionControllerCallback, never())
             .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
-        verify(qS, never()).setTransitionToFullShadeProgress(anyBoolean(), anyFloat(), anyFloat())
+        verify(qsTransitionController, never()).dragDownAmount = anyFloat()
     }
 
     @Test
@@ -270,7 +322,7 @@
         verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
         verify(transitionControllerCallback)
             .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
-        verify(qS).setTransitionToFullShadeProgress(eq(true), anyFloat(), anyFloat())
+        verify(qsTransitionController).dragDownAmount = 10f
         verify(depthController).transitionToFullShadeProgress = anyFloat()
     }
 
@@ -473,8 +525,8 @@
 
         transitionController.dragDownAmount = 10f
 
-        verify(nsslController).setOverScrollAmount(0)
-        verify(scrimController, never()).setNotificationsOverScrollAmount(anyInt())
+        verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+        verifyZeroInteractions(splitShadeOverScroller)
     }
 
     @Test
@@ -483,8 +535,8 @@
 
         transitionController.dragDownAmount = 10f
 
-        verify(nsslController).setOverScrollAmount(0)
-        verify(scrimController).setNotificationsOverScrollAmount(0)
+        verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+        verifyZeroInteractions(singleShadeOverScroller)
     }
 
     @Test
@@ -545,10 +597,11 @@
     )
     interface TestComponent {
 
-        val transitionController: LockscreenShadeTransitionController
-
         val configurationController: FakeConfigurationController
         val disableFlagsRepository: FakeDisableFlagsRepository
+        val powerInteractor: PowerInteractor
+        val shadeInteractor: ShadeInteractor
+        val shadeRepository: FakeShadeRepository
         val testScope: TestScope
 
         @Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
deleted file mode 100644
index e4da53a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar
-
-import com.google.common.truth.Truth.assertThat
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Point
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-private const val WIDTH = 200
-private const val HEIGHT = 200
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class MediaArtworkProcessorTest : SysuiTestCase() {
-
-    private var screenWidth = 0
-    private var screenHeight = 0
-
-    private lateinit var processor: MediaArtworkProcessor
-
-    @Before
-    fun setUp() {
-        processor = MediaArtworkProcessor()
-
-        val point = Point()
-        checkNotNull(context.display).getSize(point)
-        screenWidth = point.x
-        screenHeight = point.y
-    }
-
-    @After
-    fun tearDown() {
-        processor.clearCache()
-    }
-
-    @Test
-    fun testProcessArtwork() {
-        // GIVEN some "artwork", which is just a solid blue image
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // WHEN the background is created from the artwork
-        val background = processor.processArtwork(context, artwork)!!
-        // THEN the background has the size of the screen that has been downsamples
-        assertThat(background.height).isLessThan(screenHeight)
-        assertThat(background.width).isLessThan(screenWidth)
-        assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
-    }
-
-    @Test
-    fun testCache() {
-        // GIVEN a solid blue image
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // WHEN the background is processed twice
-        val background1 = processor.processArtwork(context, artwork)!!
-        val background2 = processor.processArtwork(context, artwork)!!
-        // THEN the two bitmaps are the same
-        // Note: This is currently broken and trying to use caching causes issues
-        assertThat(background1).isNotSameInstanceAs(background2)
-    }
-
-    @Test
-    fun testConfig() {
-        // GIVEN some which is not ARGB_8888
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // WHEN the background is created from the artwork
-        val background = processor.processArtwork(context, artwork)!!
-        // THEN the background has Config ARGB_8888
-        assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
-    }
-
-    @Test
-    fun testRecycledArtwork() {
-        // GIVEN some "artwork", which is just a solid blue image
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // AND the artwork is recycled
-        artwork.recycle()
-        // WHEN the background is created from the artwork
-        val background = processor.processArtwork(context, artwork)
-        // THEN the processed bitmap is null
-        assertThat(background).isNull()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 3412679..a4c12f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +41,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -67,10 +72,12 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
+        setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
         mListener = new NotificationListener(
                 mContext,
                 mNotificationManager,
+                new SilentNotificationStatusIconsVisibilityInteractor(
+                        new NotificationListenerSettingsRepository()),
                 mFakeSystemClock,
                 mFakeExecutor,
                 mPluginManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
new file mode 100644
index 0000000..3a9c24a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 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 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);
+
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser));
+        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+                mSecondaryUser));
+        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 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
index 19863ec..43adc69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,17 +16,27 @@
 
 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.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.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.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 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;
@@ -38,12 +48,15 @@
 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.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -59,6 +72,8 @@
 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;
@@ -74,16 +89,21 @@
 import com.google.android.collect.Lists;
 
 import org.junit.Before;
-import org.junit.Ignore;
 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 NotificationLockscreenUserManagerTest extends SysuiTestCase {
+    private static final int TEST_PROFILE_USERHANDLE = 12;
     @Mock
     private NotificationPresenter mPresenter;
     @Mock
@@ -121,11 +141,15 @@
     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, true);
+
         int currentUserId = ActivityManager.getCurrentUser();
         when(mUserTracker.getUserId()).thenReturn(currentUserId);
         mSettings = new FakeSettings();
@@ -138,103 +162,144 @@
         when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
         when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
                 mCurrentUser, mWorkUser));
+        when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser, mSecondaryUser));
         when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
                 mSecondaryUser));
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 Handler.createAsync(Looper.myLooper()));
 
         Notification notifWithPrivateVisibility = new Notification();
-        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+        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);
     }
 
+    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 testLockScreenShowNotificationsFalse() {
         mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenShowNotificationsTrue() {
         mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsTrue() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
-    public void testCurrentUserPrivateNotificationsNotRedacted() {
+    public void testCurrentUserPrivateNotificationsRedacted() {
         // GIVEN current user doesn't allow private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN current user's notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
     }
 
     @Test
-    public void testCurrentUserPrivateNotificationsRedacted() {
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
         // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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 testWorkPrivateNotificationsRedacted() {
         // GIVEN work profile doesn't private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN work profile notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -244,9 +309,9 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted() {
         // GIVEN work profile allows private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN work profile notification isn't redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -256,13 +321,15 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
         // GIVEN work profile allows private notifications to show but the other users don't
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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));
@@ -275,13 +342,15 @@
     @Test
     public void testWorkProfileRedacted_otherUsersNotRedacted() {
         // GIVEN work profile doesn't allow private notifications to show but the other users do
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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));
@@ -295,11 +364,12 @@
     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(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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
@@ -323,6 +393,7 @@
     @Test
     public void testUpdateIsPublicMode() {
         when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
 
         NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
         mLockscreenUserManager.addNotificationStateChangedListener(listener);
@@ -330,24 +401,28 @@
 
         // 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();
     }
@@ -356,14 +431,14 @@
     public void testDevicePolicyDoesNotAllowNotifications() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifs on lockscreen
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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));
@@ -371,19 +446,18 @@
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testDevicePolicyDoesNotAllowNotifications_userAll() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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));
@@ -392,98 +466,105 @@
     }
 
     @Test
-    @Ignore("b/286230167")
     public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        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));
 
-        // TODO (b/286230167): enable assertion
         verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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));
 
-        // TODO (b/286230167): enable assertion. It's currently called 4 times.
-        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+        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);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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()));
+        assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
     }
 
     @Test
     public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        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));
 
-        // TODO (b/286230167): enable assertion. It's currently called 5 times.
-        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testHideNotifications_primary() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
@@ -491,16 +572,17 @@
     @Test
     public void testHideNotifications_secondary() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testHideNotifications_workProfile() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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));
     }
@@ -508,67 +590,62 @@
     @Test
     public void testHideNotifications_secondary_userSwitch() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
-        TestNotificationLockscreenUserManager lockscreenUserManager
-                = new TestNotificationLockscreenUserManager(mContext);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
 
-        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+        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);
 
-        TestNotificationLockscreenUserManager lockscreenUserManager
-                = new TestNotificationLockscreenUserManager(mContext);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
 
-        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     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);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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
+        // 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));
     }
 
@@ -576,31 +653,30 @@
     public void testUserAllowsNotificationsInPublic_settingsChange() {
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
 
         // User disables
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        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 = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        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));
@@ -608,6 +684,82 @@
         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);
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
+    }
+
+    @Test
+    public void testProfileAvailabilityIntent() {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_AVAILABLE);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+    }
+
+    @Test
+    public void testProfileUnAvailabilityIntent() {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+    }
+
+    @Test
+    public void testManagedProfileAvailabilityIntent() {
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        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);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+    }
+
+    @Test
+    public void testManagedProfileUnAvailabilityIntent() {
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        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);
+        assertEquals(2, 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) {
@@ -623,12 +775,17 @@
                     (() -> mOverviewProxyService),
                     NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                     mStatusBarStateController,
-                    Handler.createAsync(Looper.myLooper()),
+                    Handler.createAsync(TestableLooper.get(
+                            NotificationLockscreenUserManagerTest.this).getLooper()),
+                    Handler.createAsync(TestableLooper.get(
+                            NotificationLockscreenUserManagerTest.this).getLooper()),
+                    mBackgroundExecutor,
                     mDeviceProvisionedController,
                     mKeyguardStateController,
                     mSettings,
                     mock(DumpManager.class),
-                    mock(LockPatternUtils.class));
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
         }
 
         public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
deleted file mode 100644
index 9d6ea85..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ /dev/null
@@ -1,70 +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 android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.doCallRealMethod
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Temporary test for the lock screen live wallpaper project.
- *
- * TODO(b/273443374): remove this test
- */
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class NotificationMediaManagerTest : SysuiTestCase() {
-
-    @Mock private lateinit var notificationMediaManager: NotificationMediaManager
-
-    @Mock private lateinit var mockBackDropView: BackDropView
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        doCallRealMethod()
-            .whenever(notificationMediaManager)
-            .updateMediaMetaData(anyBoolean(), anyBoolean())
-        doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView
-    }
-
-    @After fun tearDown() {}
-
-    /** Check that updateMediaMetaData is a no-op with mIsLockscreenLiveWallpaperEnabled = true */
-    @Test
-    fun testUpdateMediaMetaDataDisabled() {
-        notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true
-        for (metaDataChanged in listOf(true, false)) {
-            for (allowEnterAnimation in listOf(true, false)) {
-                notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation)
-                verify(notificationMediaManager, never()).mediaMetadata
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 764f7b6..560ebc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -33,9 +33,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -87,7 +88,8 @@
                 new RemoteInputControllerLogger(logcatLogBuffer()),
                 mClickNotifier,
                 new ActionClickLogger(logcatLogBuffer()),
-                mock(DumpManager.class));
+                mock(JavaAdapter.class),
+                mock(ShadeInteractor.class));
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
                 .setOpPkg(TEST_PACKAGE_NAME)
@@ -145,7 +147,8 @@
                 RemoteInputControllerLogger remoteInputControllerLogger,
                 NotificationClickNotifier clickNotifier,
                 ActionClickLogger actionClickLogger,
-                DumpManager dumpManager) {
+                JavaAdapter javaAdapter,
+                ShadeInteractor shadeInteractor) {
             super(
                     context,
                     notifPipelineFlags,
@@ -158,7 +161,8 @@
                     remoteInputControllerLogger,
                     clickNotifier,
                     actionClickLogger,
-                    dumpManager);
+                    javaAdapter,
+                    shadeInteractor);
         }
 
         public void setUpWithPresenterForTest(Callback callback,
@@ -170,3 +174,4 @@
 
     }
 }
+
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 3327e42..d6dfc5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -23,9 +23,30 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+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.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+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.util.mockito.mock
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -40,17 +61,22 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class StatusBarStateControllerImplTest : SysuiTestCase() {
 
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private lateinit var shadeInteractor: ShadeInteractor
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock private lateinit var mockDarkAnimator: ObjectAnimator
-    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var mockDarkAnimator: ObjectAnimator
 
     private lateinit var controller: StatusBarStateControllerImpl
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -64,11 +90,75 @@
         uiEventLogger = UiEventLoggerFake()
         controller = object : StatusBarStateControllerImpl(
             uiEventLogger,
-            mock(DumpManager::class.java),
-            interactionJankMonitor, shadeExpansionStateManager
+            interactionJankMonitor,
+            mock(),
+            { shadeInteractor }
         ) {
             override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator }
         }
+
+        val powerInteractor = PowerInteractor(
+            FakePowerRepository(),
+            FalsingCollectorFake(),
+            mock(),
+            controller)
+        val keyguardRepository = FakeKeyguardRepository()
+        val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        val featureFlags = FakeFeatureFlagsClassic()
+        val shadeRepository = FakeShadeRepository()
+        val sceneContainerFlags = FakeSceneContainerFlags()
+        val configurationRepository = FakeConfigurationRepository()
+        val keyguardInteractor = KeyguardInteractor(
+            keyguardRepository,
+            FakeCommandQueue(),
+            powerInteractor,
+            featureFlags,
+            sceneContainerFlags,
+            FakeKeyguardBouncerRepository(),
+            configurationRepository,
+            shadeRepository,
+            utils::sceneInteractor)
+        val keyguardTransitionInteractor = KeyguardTransitionInteractor(
+            testScope.backgroundScope,
+            keyguardTransitionRepository,
+            { keyguardInteractor },
+            { fromLockscreenTransitionInteractor },
+            { fromPrimaryBouncerTransitionInteractor })
+        fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor(
+            keyguardTransitionRepository,
+            keyguardTransitionInteractor,
+            testScope.backgroundScope,
+            keyguardInteractor,
+            featureFlags,
+            shadeRepository,
+            powerInteractor)
+        fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor(
+            keyguardTransitionRepository,
+            keyguardTransitionInteractor,
+            testScope.backgroundScope,
+            keyguardInteractor,
+            featureFlags,
+            mock(),
+            mock(),
+            powerInteractor)
+        shadeInteractor = ShadeInteractor(
+            testScope.backgroundScope,
+            FakeDeviceProvisioningRepository(),
+            FakeDisableFlagsRepository(),
+            mock(),
+            sceneContainerFlags,
+            utils::sceneInteractor,
+            keyguardRepository,
+            keyguardTransitionInteractor,
+            powerInteractor,
+            FakeUserSetupRepository(),
+            mock(),
+            SharedNotificationContainerInteractor(
+                configurationRepository,
+                mContext,
+                ResourcesSplitShadeStateController()),
+            shadeRepository,
+        )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
index 8397702..df8afde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -26,6 +26,7 @@
     override var forceVisible: Boolean = false,
     override val showAnimation: Boolean = true,
     override var contentDescription: String? = "",
+    override val shouldAnnounceAccessibilityEvent: Boolean = false
 ) : StatusEvent
 
 class FakePrivacyStatusEvent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 4fcccf8..fee8b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
@@ -370,6 +372,63 @@
     }
 
     @Test
+    fun testAccessibilityAnnouncement_announced() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+        val accessibilityDesc = "Some desc"
+        val mockView = mock<View>()
+        val mockAnimatableView =
+            mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+        scheduleFakeEventWithView(
+            accessibilityDesc,
+            mockAnimatableView,
+            shouldAnnounceAccessibilityEvent = true
+        )
+        fastForwardAnimationToState(ANIMATING_OUT)
+
+        verify(mockView).announceForAccessibility(eq(accessibilityDesc))
+    }
+
+    @Test
+    fun testAccessibilityAnnouncement_nullDesc_noAnnouncement() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+        val accessibilityDesc = null
+        val mockView = mock<View>()
+        val mockAnimatableView =
+            mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+        scheduleFakeEventWithView(
+            accessibilityDesc,
+            mockAnimatableView,
+            shouldAnnounceAccessibilityEvent = true
+        )
+        fastForwardAnimationToState(ANIMATING_OUT)
+
+        verify(mockView, never()).announceForAccessibility(any())
+    }
+
+    @Test
+    fun testAccessibilityAnnouncement_notNeeded_noAnnouncement() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+        val accessibilityDesc = "something"
+        val mockView = mock<View>()
+        val mockAnimatableView =
+            mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+        scheduleFakeEventWithView(
+            accessibilityDesc,
+            mockAnimatableView,
+            shouldAnnounceAccessibilityEvent = false
+        )
+        fastForwardAnimationToState(ANIMATING_OUT)
+
+        verify(mockView, never()).announceForAccessibility(any())
+    }
+
+    @Test
     fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest {
         // Instantiate class under test with TestScope from runTest
         initializeSystemStatusAnimationScheduler(testScope = this)
@@ -572,6 +631,20 @@
         return privacyChip
     }
 
+    private fun scheduleFakeEventWithView(
+        desc: String?,
+        view: BackgroundAnimatableView,
+        shouldAnnounceAccessibilityEvent: Boolean
+    ) {
+        val fakeEvent =
+            FakeStatusEvent(
+                viewCreator = { view },
+                contentDescription = desc,
+                shouldAnnounceAccessibilityEvent = shouldAnnounceAccessibilityEvent
+            )
+        systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
+    }
+
     private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
         val batteryChip = BatteryStatusChip(mContext)
         val fakeBatteryEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
deleted file mode 100644
index aeb5b03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ /dev/null
@@ -1,87 +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.notification;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.palette.graphics.Palette;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class MediaNotificationProcessorTest extends SysuiTestCase {
-
-    private static final int BITMAP_WIDTH = 10;
-    private static final int BITMAP_HEIGHT = 10;
-
-    /**
-     * Color tolerance is borrowed from the AndroidX test utilities for Palette.
-     */
-    private static final int COLOR_TOLERANCE = 8;
-
-    @Nullable private Bitmap mArtwork;
-
-    @After
-    public void tearDown() {
-        if (mArtwork != null) {
-            mArtwork.recycle();
-            mArtwork = null;
-        }
-    }
-
-    @Test
-    public void findBackgroundSwatch_white() {
-        // Given artwork that is completely white.
-        mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(mArtwork);
-        canvas.drawColor(Color.WHITE);
-        // WHEN the background swatch is computed
-        Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
-        // THEN the swatch color is white
-        assertCloseColors(swatch.getRgb(), Color.WHITE);
-    }
-
-    @Test
-    public void findBackgroundSwatch_red() {
-        // Given artwork that is completely red.
-        mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(mArtwork);
-        canvas.drawColor(Color.RED);
-        // WHEN the background swatch is computed
-        Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
-        // THEN the swatch color is red
-        assertCloseColors(swatch.getRgb(), Color.RED);
-    }
-
-    static void assertCloseColors(int expected, int actual) {
-        assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual));
-        assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
-        assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
-    }
-}
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 235ac5c..6059363 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
@@ -11,7 +11,8 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -44,7 +45,8 @@
     private lateinit var notificationTestHelper: NotificationTestHelper
     private lateinit var notification: ExpandableNotificationRow
     private lateinit var controller: NotificationLaunchAnimatorController
-    private val notificationExpansionRepository = NotificationExpansionRepository()
+    private val notificationLaunchAnimationInteractor =
+        NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository())
 
     private val testScope = TestScope()
 
@@ -57,16 +59,17 @@
     fun setUp() {
         allowTestableLooperAsMainThread()
         notificationTestHelper =
-                NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+            NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
         notification = notificationTestHelper.createRow()
-        controller = NotificationLaunchAnimatorController(
-                notificationExpansionRepository,
+        controller =
+            NotificationLaunchAnimatorController(
+                notificationLaunchAnimationInteractor,
                 notificationListContainer,
                 headsUpManager,
                 notification,
                 jankMonitor,
                 onFinishAnimationCallback
-        )
+            )
     }
 
     private fun flagNotificationAsHun() {
@@ -80,13 +83,14 @@
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
-        val isExpandAnimationRunning by testScope.collectLastValue(
-            notificationExpansionRepository.isExpandAnimationRunning
-        )
+        val isExpandAnimationRunning by
+            testScope.collectLastValue(
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+            )
         assertFalse(isExpandAnimationRunning!!)
 
-        verify(headsUpManager).removeNotification(
-                notificationKey, true /* releaseImmediately */, true /* animate */)
+        verify(headsUpManager)
+            .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
         verify(onFinishAnimationCallback).run()
     }
 
@@ -97,13 +101,14 @@
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
-        val isExpandAnimationRunning by testScope.collectLastValue(
-            notificationExpansionRepository.isExpandAnimationRunning
-        )
+        val isExpandAnimationRunning by
+            testScope.collectLastValue(
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+            )
         assertFalse(isExpandAnimationRunning!!)
 
-        verify(headsUpManager).removeNotification(
-                notificationKey, true /* releaseImmediately */, true /* animate */)
+        verify(headsUpManager)
+            .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
         verify(onFinishAnimationCallback).run()
     }
 
@@ -114,13 +119,14 @@
 
         assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
-        val isExpandAnimationRunning by testScope.collectLastValue(
-            notificationExpansionRepository.isExpandAnimationRunning
-        )
+        val isExpandAnimationRunning by
+            testScope.collectLastValue(
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+            )
         assertFalse(isExpandAnimationRunning!!)
 
-        verify(headsUpManager).removeNotification(
-                notificationKey, true /* releaseImmediately */, false /* animate */)
+        verify(headsUpManager)
+            .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */)
         verify(onFinishAnimationCallback).run()
     }
 
@@ -128,15 +134,21 @@
     fun testAlertingSummaryHunRemovedOnNonAlertingChildLaunch() {
         val GROUP_KEY = "test_group_key"
 
-        val summary = NotificationEntryBuilder().setGroup(mContext, GROUP_KEY).setId(0).apply {
-            modifyNotification(mContext).setSmallIcon(R.drawable.ic_person)
-        }.build()
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, GROUP_KEY)
+                .setId(0)
+                .apply { modifyNotification(mContext).setSmallIcon(R.drawable.ic_person) }
+                .build()
         assertNotSame(summary.key, notification.entry.key)
 
         notificationTestHelper.createRow(summary)
 
-        GroupEntryBuilder().setKey(GROUP_KEY).setSummary(summary).addChild(notification.entry)
-                .build()
+        GroupEntryBuilder()
+            .setKey(GROUP_KEY)
+            .setSummary(summary)
+            .addChild(notification.entry)
+            .build()
         assertSame(summary, notification.entry.parent?.summary)
 
         `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
@@ -147,10 +159,14 @@
 
         controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
 
-        verify(headsUpManager).removeNotification(
-                summary.key, true /* releaseImmediately */, false /* animate */)
-        verify(headsUpManager, never()).removeNotification(
-                notification.entry.key, true /* releaseImmediately */, false /* animate */)
+        verify(headsUpManager)
+            .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
+        verify(headsUpManager, never())
+            .removeNotification(
+                notification.entry.key,
+                true /* releaseImmediately */,
+                false /* animate */
+            )
     }
 
     @Test
@@ -158,9 +174,10 @@
         controller.onIntentStarted(willAnimate = true)
 
         assertTrue(notification.entry.isExpandAnimationRunning)
-        val isExpandAnimationRunning by testScope.collectLastValue(
-            notificationExpansionRepository.isExpandAnimationRunning
-        )
+        val isExpandAnimationRunning by
+            testScope.collectLastValue(
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+            )
         assertTrue(isExpandAnimationRunning!!)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS
new file mode 100644
index 0000000..7f5384d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c664c39..2ef4374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,10 +18,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 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.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
+import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -33,8 +39,8 @@
 import android.view.animation.Interpolator;
 
 import com.android.app.animation.Interpolators;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -85,7 +91,7 @@
             return mEffectiveProperty;
         }
     };
-    private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+    private AnimatorListenerAdapter mFinishListener;
     private AnimationProperties mAnimationProperties = new AnimationProperties() {
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@
     @Before
     public void setUp() {
         mView = new View(getContext());
+        mFinishListener = mock(AnimatorListenerAdapter.class);
     }
 
     @Test
@@ -229,6 +236,32 @@
     }
 
     @Test
+    public void testListenerCallbackOrderAndTagState() {
+        mAnimationFilter.reset();
+        mAnimationFilter.animate(mProperty.getProperty());
+        mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator);
+        mAnimationProperties.setDuration(500);
+
+        // Validates that the onAnimationEnd function set by PropertyAnimator was run first.
+        doAnswer(invocation -> {
+            assertNull(mView.getTag(mProperty.getAnimatorTag()));
+            return null;
+        })
+                .when(mFinishListener)
+                .onAnimationEnd(any(Animator.class), anyBoolean());
+
+        // Begin the animation and verify it set state correctly
+        PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+        ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag());
+        assertNotNull(animator);
+        assertNotNull(mView.getTag(mProperty.getAnimatorTag()));
+
+        // Terminate the animation to run end runners, and validate they executed.
+        animator.end();
+        verify(mFinishListener).onAnimationEnd(animator, false);
+    }
+
+    @Test
     public void testIsAnimating() {
         mAnimationFilter.reset();
         mAnimationFilter.animate(mProperty.getProperty());
@@ -236,4 +269,4 @@
         PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
         assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 546abd4..6c1f537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -39,10 +39,10 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.mockito.any
@@ -246,7 +246,7 @@
             unseenFilter.onCleanup()
 
             // THEN: The SeenNotificationProvider has been updated to reflect the suppression
-            assertThat(notificationListInteractor.hasFilteredOutSeenNotifications.value).isTrue()
+            assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue()
         }
     }
 
@@ -597,7 +597,8 @@
             FakeSettings().apply {
                 putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
             }
-        val notificationListInteractor = NotificationListInteractor(NotificationListRepository())
+        val seenNotificationsInteractor =
+            SeenNotificationsInteractor(ActiveNotificationListRepository())
         val keyguardCoordinator =
             KeyguardCoordinator(
                 testDispatcher,
@@ -610,7 +611,7 @@
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,
                 fakeSettings,
-                notificationListInteractor,
+                seenNotificationsInteractor,
                 statusBarStateController,
             )
         keyguardCoordinator.attach(notifPipeline)
@@ -618,7 +619,7 @@
             KeyguardCoordinatorTestScope(
                     keyguardCoordinator,
                     testScope,
-                    notificationListInteractor,
+                    seenNotificationsInteractor,
                     fakeSettings,
                 )
                 .testBlock()
@@ -628,7 +629,7 @@
     private inner class KeyguardCoordinatorTestScope(
         private val keyguardCoordinator: KeyguardCoordinator,
         private val scope: TestScope,
-        val notificationListInteractor: NotificationListInteractor,
+        val seenNotificationsInteractor: SeenNotificationsInteractor,
         private val fakeSettings: FakeSettings,
     ) : CoroutineScope by scope {
         val testScheduler: TestCoroutineScheduler
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 27be4c8..df547ae 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
@@ -33,6 +33,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.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -53,6 +54,7 @@
     val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
     val statusBarStateController: StatusBarStateController = mock()
     val keyguardStateController: KeyguardStateController = mock()
+    val mSelectedUserInteractor: SelectedUserInteractor = mock()
 
     val coordinator: SensitiveContentCoordinator =
         DaggerTestSensitiveContentCoordinatorComponent
@@ -62,7 +64,8 @@
                         lockscreenUserManager,
                         keyguardUpdateMonitor,
                         statusBarStateController,
-                        keyguardStateController)
+                        keyguardStateController,
+                        mSelectedUserInteractor)
                 .coordinator
 
     @Test
@@ -263,7 +266,8 @@
             @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
             @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
             @BindsInstance statusBarStateController: StatusBarStateController,
-            @BindsInstance keyguardStateController: KeyguardStateController
+            @BindsInstance keyguardStateController: KeyguardStateController,
+            @BindsInstance selectedUserInteractor: SelectedUserInteractor,
         ): 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 655bd72..e81207e 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
@@ -19,6 +19,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.setFlagValue
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -27,6 +28,8 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
@@ -36,9 +39,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -52,27 +56,47 @@
     @Mock private lateinit var pipeline: NotifPipeline
     @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
     @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
+    @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
     @Mock private lateinit var stackController: NotifStackController
     @Mock private lateinit var section: NotifSection
 
     @Before
     fun setUp() {
         initMocks(this)
-        coordinator = StackCoordinator(groupExpansionManagerImpl, notificationIconAreaController)
+        setUp(NotificationIconContainerRefactor.FLAG_NAME to null)
+        entry = NotificationEntryBuilder().setSection(section).build()
+    }
+
+    private fun setUp(vararg flags: Pair<String, Boolean?>) {
+        flags.forEach { (name, value) -> mSetFlagsRule.setFlagValue(name, value) }
+        reset(pipeline)
+        coordinator =
+            StackCoordinator(
+                groupExpansionManagerImpl,
+                notificationIconAreaController,
+                renderListInteractor,
+            )
         coordinator.attach(pipeline)
         afterRenderListListener = withArgCaptor {
             verify(pipeline).addOnAfterRenderListListener(capture())
         }
-        entry = NotificationEntryBuilder().setSection(section).build()
     }
 
     @Test
     fun testUpdateNotificationIcons() {
+        setUp(NotificationIconContainerRefactor.FLAG_NAME to false)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
     }
 
     @Test
+    fun testSetRenderedListOnInteractor() {
+        setUp(NotificationIconContainerRefactor.FLAG_NAME to true)
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
+    }
+
+    @Test
     fun testSetNotificationStats_clearableAlerting() {
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
deleted file mode 100644
index f28d9ab..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
+++ /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.systemui.statusbar.notification.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-
-@SmallTest
-class NotificationExpansionRepositoryTest : SysuiTestCase() {
-    private val underTest = NotificationExpansionRepository()
-
-    @Test
-    fun setIsExpandAnimationRunning_startsAsFalse() = runTest {
-        val latest by collectLastValue(underTest.isExpandAnimationRunning)
-
-        assertThat(latest).isFalse()
-    }
-
-    @Test
-    fun setIsExpandAnimationRunning_false_emitsTrue() = runTest {
-        val latest by collectLastValue(underTest.isExpandAnimationRunning)
-
-        underTest.setIsExpandAnimationRunning(true)
-
-        assertThat(latest).isTrue()
-    }
-
-    @Test
-    fun setIsExpandAnimationRunning_false_emitsFalse() = runTest {
-        val latest by collectLastValue(underTest.isExpandAnimationRunning)
-        underTest.setIsExpandAnimationRunning(true)
-
-        // WHEN the animation is no longer running
-        underTest.setIsExpandAnimationRunning(false)
-
-        // THEN the flow emits false
-        assertThat(latest).isFalse()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
new file mode 100644
index 0000000..683d0aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.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.notification.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class NotificationAlertsInteractorTest : SysuiTestCase() {
+
+    @Component(modules = [SysUITestModule::class])
+    @SysUISingleton
+    interface TestComponent {
+        val underTest: NotificationAlertsInteractor
+        val disableFlags: FakeDisableFlagsRepository
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase): TestComponent
+        }
+    }
+
+    private val testComponent: TestComponent =
+        DaggerNotificationAlertsInteractorTest_TestComponent.factory().create(test = this)
+
+    @Test
+    fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
+        with(testComponent) {
+            disableFlags.disableFlags.value =
+                DisableFlagsModel(
+                    StatusBarManager.DISABLE_NONE,
+                    StatusBarManager.DISABLE2_NONE,
+                )
+            assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() =
+        with(testComponent) {
+            disableFlags.disableFlags.value =
+                DisableFlagsModel(
+                    StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+                    StatusBarManager.DISABLE2_NONE,
+                )
+            assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
new file mode 100644
index 0000000..a0faab5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class NotificationLaunchAnimationInteractorTest : SysuiTestCase() {
+    private val repository = NotificationLaunchAnimationRepository()
+    private val underTest = NotificationLaunchAnimationInteractor(repository)
+
+    @Test
+    fun setIsLaunchAnimationRunning_startsAsFalse() = runTest {
+        val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+
+        assertThat(latest).isFalse()
+    }
+
+    @Test
+    fun setIsLaunchAnimationRunning_false_emitsTrue() = runTest {
+        val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+
+        underTest.setIsLaunchAnimationRunning(true)
+
+        assertThat(latest).isTrue()
+    }
+
+    @Test
+    fun setIsLaunchAnimationRunning_false_emitsFalse() = runTest {
+        val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+        underTest.setIsLaunchAnimationRunning(true)
+
+        // WHEN the animation is no longer running
+        underTest.setIsLaunchAnimationRunning(false)
+
+        // THEN the flow emits false
+        assertThat(latest).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
deleted file mode 100644
index fe49016..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import android.app.StatusBarManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class NotificationsInteractorTest : SysuiTestCase() {
-
-    private lateinit var underTest: NotificationsInteractor
-
-    private val testScope = TestScope(UnconfinedTestDispatcher())
-    private val commandQueue: CommandQueue = mock()
-    private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
-    private val disableFlagsLogger = DisableFlagsLogger()
-    private lateinit var disableFlagsRepository: DisableFlagsRepository
-
-    @Before
-    fun setUp() {
-        disableFlagsRepository =
-            DisableFlagsRepositoryImpl(
-                commandQueue,
-                DISPLAY_ID,
-                testScope.backgroundScope,
-                mock(),
-                logBuffer,
-                disableFlagsLogger,
-            )
-        underTest = NotificationsInteractor(disableFlagsRepository)
-    }
-
-    @Test
-    fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() {
-        val callback = getCommandQueueCallback()
-
-        callback.disable(
-            DISPLAY_ID,
-            StatusBarManager.DISABLE_NONE,
-            StatusBarManager.DISABLE2_NONE,
-            /* animate= */ false
-        )
-
-        assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
-    }
-
-    @Test
-    fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() {
-        val callback = getCommandQueueCallback()
-
-        callback.disable(
-            DISPLAY_ID,
-            StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
-            StatusBarManager.DISABLE2_NONE,
-            /* animate= */ false
-        )
-
-        assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
-    }
-
-    private fun getCommandQueueCallback(): CommandQueue.Callbacks {
-        val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
-        verify(commandQueue).addCallback(callbackCaptor.capture())
-        return callbackCaptor.value
-    }
-
-    private companion object {
-        const val DISPLAY_ID = 1
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
new file mode 100644
index 0000000..ca8ea4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+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.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.byKey
+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.Test
+
+@SmallTest
+class RenderNotificationsListInteractorTest : SysuiTestCase() {
+
+    private val notifsRepository = ActiveNotificationListRepository()
+    private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository)
+    private val underTest =
+        RenderNotificationListInteractor(
+            notifsRepository,
+            sectionStyleProvider = mock(),
+        )
+
+    @Test
+    fun setRenderedList_preservesOrdering() = runTest {
+        val notifs by collectLastValue(notifsInteractor.notifications)
+        val keys = (1..50).shuffled().map { "$it" }
+        val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } }
+        underTest.setRenderedList(entries)
+        assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2a3c1a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.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.statusbar.notification.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+    private val repository = ActiveNotificationListRepository()
+    private val underTest = SeenNotificationsInteractor(repository)
+
+    @Test
+    fun testNoFilteredOutSeenNotifications() = runTest {
+        val hasFilteredOutSeenNotifications by
+            collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+        underTest.setHasFilteredOutSeenNotifications(false)
+
+        assertThat(hasFilteredOutSeenNotifications).isFalse()
+    }
+
+    @Test
+    fun testHasFilteredOutSeenNotifications() = runTest {
+        val hasFilteredOutSeenNotifications by
+            collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+        underTest.setHasFilteredOutSeenNotifications(true)
+
+        assertThat(hasFilteredOutSeenNotifications).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index f72142f..cc87d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -22,8 +22,15 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -31,6 +38,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
 
@@ -44,9 +52,13 @@
 
     FooterView mView;
 
+    Context mSpyContext = spy(mContext);
+
     @Before
     public void setUp() {
-        mView = (FooterView) LayoutInflater.from(mContext).inflate(
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
+
+        mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
                 R.layout.status_bar_notification_footer, null, false);
         mView.setDuration(0);
     }
@@ -102,6 +114,37 @@
     }
 
     @Test
+    public void testSetMessageString_resourceOnlyFetchedOnce() {
+        mView.setMessageString(R.string.unlock_to_see_notif_text);
+        verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.unlock_prompt_footer))
+                .getText().toString()).contains("Unlock");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setMessageString(R.string.unlock_to_see_notif_text);
+        mView.setMessageString(R.string.unlock_to_see_notif_text);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    public void testSetMessageIcon_resourceOnlyFetchedOnce() {
+        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+        verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+
+        clearInvocations(mSpyContext);
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+
+        verify(mSpyContext, never()).getDrawable(anyInt());
+    }
+
+    @Test
     public void testSetFooterLabelVisible() {
         mView.setFooterLabelVisible(true);
         assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
new file mode 100644
index 0000000..57a7c3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.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.statusbar.notification.footer.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FooterViewModelTest : SysuiTestCase() {
+    private val repository = ActiveNotificationListRepository()
+    private val interactor = SeenNotificationsInteractor(repository)
+    private val underTest = FooterViewModel(interactor)
+
+    @Test
+    fun testMessageVisible_whenFilteredNotifications() = runTest {
+        val message by collectLastValue(underTest.message)
+
+        repository.hasFilteredOutSeenNotifications.value = true
+
+        assertThat(message?.visible).isTrue()
+    }
+
+    @Test
+    fun testMessageVisible_whenNoFilteredNotifications() = runTest {
+        val message by collectLastValue(underTest.message)
+
+        repository.hasFilteredOutSeenNotifications.value = false
+
+        assertThat(message?.visible).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
new file mode 100644
index 0000000..ec80e5f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.icon.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
+import com.android.systemui.statusbar.notification.shared.byIsAmbient
+import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
+import com.android.systemui.statusbar.notification.shared.byIsPulsing
+import com.android.systemui.statusbar.notification.shared.byIsRowDismissed
+import com.android.systemui.statusbar.notification.shared.byIsSilent
+import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationIconsInteractorTest : SysuiTestCase() {
+
+    private val bubbles: Bubbles = mock()
+
+    @Component(modules = [SysUITestModule::class])
+    @SysUISingleton
+    interface TestComponent {
+        val underTest: NotificationIconsInteractor
+
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+        val testScope: TestScope
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+        }
+    }
+
+    val testComponent: TestComponent =
+        DaggerNotificationIconsInteractorTest_TestComponent.factory()
+            .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+    @Before
+    fun setup() =
+        with(testComponent) {
+            activeNotificationListRepository.activeNotifications.value =
+                testIcons.associateBy { it.key }
+        }
+
+    @Test
+    fun filteredEntrySet() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.filteredNotifSet())
+                assertThat(filteredSet).containsExactlyElementsIn(testIcons)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noExpandedBubbles() =
+        with(testComponent) {
+            testScope.runTest {
+                whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+                val filteredSet by collectLastValue(underTest.filteredNotifSet())
+                assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noAmbient() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
+                assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsSuppressedFromStatusBar)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noLowPriority() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by
+                    collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
+                assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noDismissed() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by
+                    collectLastValue(underTest.filteredNotifSet(showDismissed = false))
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsRowDismissed)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noRepliedMessages() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by
+                    collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsLastMessageFromReply)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+                keyguardViewStateRepository.setNotificationsFullyHidden(false)
+                assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noPulsing_notifsFullyHidden() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+                keyguardViewStateRepository.setNotificationsFullyHidden(true)
+                assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+            }
+        }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+
+    private val bubbles: Bubbles = mock()
+
+    @Component(modules = [SysUITestModule::class])
+    @SysUISingleton
+    interface TestComponent {
+        val underTest: AlwaysOnDisplayNotificationIconsInteractor
+
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val deviceEntryRepository: FakeDeviceEntryRepository
+        val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+        val testScope: TestScope
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+        }
+    }
+
+    val testComponent: TestComponent =
+        DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
+            .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+    @Before
+    fun setup() =
+        with(testComponent) {
+            activeNotificationListRepository.activeNotifications.value =
+                testIcons.associateBy { it.key }
+        }
+
+    @Test
+    fun filteredEntrySet_noExpandedBubbles() =
+        with(testComponent) {
+            testScope.runTest {
+                whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noAmbient() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsSuppressedFromStatusBar)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noDismissed() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsRowDismissed)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noRepliedMessages() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsLastMessageFromReply)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                deviceEntryRepository.setBypassEnabled(false)
+                keyguardViewStateRepository.setNotificationsFullyHidden(false)
+                assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                deviceEntryRepository.setBypassEnabled(false)
+                keyguardViewStateRepository.setNotificationsFullyHidden(true)
+                assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                deviceEntryRepository.setBypassEnabled(true)
+                keyguardViewStateRepository.setNotificationsFullyHidden(false)
+                assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.aodNotifs)
+                deviceEntryRepository.setBypassEnabled(true)
+                keyguardViewStateRepository.setNotificationsFullyHidden(true)
+                assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+            }
+        }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
+
+    private val bubbles: Bubbles = mock()
+
+    @Component(modules = [SysUITestModule::class])
+    @SysUISingleton
+    interface TestComponent {
+        val underTest: StatusBarNotificationIconsInteractor
+
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+        val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
+        val testScope: TestScope
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+        }
+    }
+
+    val testComponent: TestComponent =
+        DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
+            .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+    @Before
+    fun setup() =
+        with(testComponent) {
+            activeNotificationListRepository.activeNotifications.value =
+                testIcons.associateBy { it.key }
+        }
+
+    @Test
+    fun filteredEntrySet_noExpandedBubbles() =
+        with(testComponent) {
+            testScope.runTest {
+                whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+                val filteredSet by collectLastValue(underTest.statusBarNotifs)
+                assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noAmbient() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.statusBarNotifs)
+                assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsSuppressedFromStatusBar)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.statusBarNotifs)
+                notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+                assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.statusBarNotifs)
+                notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+                assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noDismissed() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.statusBarNotifs)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsRowDismissed)
+                    .doesNotContain(true)
+            }
+        }
+
+    @Test
+    fun filteredEntrySet_noRepliedMessages() =
+        with(testComponent) {
+            testScope.runTest {
+                val filteredSet by collectLastValue(underTest.statusBarNotifs)
+                assertThat(filteredSet)
+                    .comparingElementsUsing(byIsLastMessageFromReply)
+                    .doesNotContain(true)
+            }
+        }
+}
+
+private val testIcons =
+    listOf(
+        activeNotificationModel(
+            key = "notif1",
+        ),
+        activeNotificationModel(
+            key = "notif2",
+            isAmbient = true,
+        ),
+        activeNotificationModel(
+            key = "notif3",
+            isRowDismissed = true,
+        ),
+        activeNotificationModel(
+            key = "notif4",
+            isSilent = true,
+        ),
+        activeNotificationModel(
+            key = "notif5",
+            isLastMessageFromReply = true,
+        ),
+        activeNotificationModel(
+            key = "notif6",
+            isSuppressedFromStatusBar = true,
+        ),
+        activeNotificationModel(
+            key = "notif7",
+            isPulsing = true,
+        ),
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
deleted file mode 100644
index e57986d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ /dev/null
@@ -1,106 +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.icon.ui.viewbinder
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.SysUITestModule
-import com.android.TestMocksModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
-
-    @Mock private lateinit var dozeParams: DozeParameters
-
-    private lateinit var testComponent: TestComponent
-    private val underTest
-        get() = testComponent.underTest
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        allowTestableLooperAsMainThread()
-
-        testComponent =
-            DaggerNotificationIconAreaControllerViewBinderWrapperImplTest_TestComponent.factory()
-                .create(
-                    test = this,
-                    featureFlags =
-                        FakeFeatureFlagsClassicModule {
-                            set(Flags.FACE_AUTH_REFACTOR, value = false)
-                            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, value = false)
-                        },
-                    mocks =
-                        TestMocksModule(
-                            dozeParameters = dozeParams,
-                        ),
-                )
-    }
-
-    @Test
-    fun testNotificationIcons_settingHideIcons() {
-        underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
-        assertFalse(underTest.shouldShowLowPriorityIcons())
-    }
-
-    @Test
-    fun testNotificationIcons_settingShowIcons() {
-        underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
-        assertTrue(underTest.shouldShowLowPriorityIcons())
-    }
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent {
-
-        val underTest: NotificationIconAreaControllerViewBinderWrapperImpl
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                mocks: TestMocksModule,
-                featureFlags: FakeFeatureFlagsClassicModule,
-            ): TestComponent
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 31efebb..41c7071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -44,7 +44,9 @@
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
@@ -243,6 +245,7 @@
                 )
             )
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            runCurrent()
 
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setKeyguardOccluded(false)
@@ -266,6 +269,7 @@
     fun isDozing_startAodTransition() =
         scope.runTest {
             val isDozing by collectLastValue(underTest.isDozing)
+            runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     from = KeyguardState.GONE,
@@ -274,13 +278,15 @@
                 )
             )
             runCurrent()
-            assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+            assertThat(isDozing?.value).isTrue()
+            assertThat(isDozing?.isAnimating).isTrue()
         }
 
     @Test
     fun isDozing_startDozeTransition() =
         scope.runTest {
             val isDozing by collectLastValue(underTest.isDozing)
+            runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     from = KeyguardState.GONE,
@@ -289,13 +295,15 @@
                 )
             )
             runCurrent()
-            assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = false))
+            assertThat(isDozing?.value).isTrue()
+            assertThat(isDozing?.isAnimating).isFalse()
         }
 
     @Test
     fun isDozing_startDozeToAodTransition() =
         scope.runTest {
             val isDozing by collectLastValue(underTest.isDozing)
+            runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     from = KeyguardState.DOZING,
@@ -304,13 +312,15 @@
                 )
             )
             runCurrent()
-            assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+            assertThat(isDozing?.value).isTrue()
+            assertThat(isDozing?.isAnimating).isTrue()
         }
 
     @Test
     fun isNotDozing_startAodToGoneTransition() =
         scope.runTest {
             val isDozing by collectLastValue(underTest.isDozing)
+            runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     from = KeyguardState.AOD,
@@ -319,13 +329,15 @@
                 )
             )
             runCurrent()
-            assertThat(isDozing).isEqualTo(AnimatedValue(false, isAnimating = true))
+            assertThat(isDozing?.value).isFalse()
+            assertThat(isDozing?.isAnimating).isTrue()
         }
 
     @Test
     fun isDozing_stopAnimation() =
         scope.runTest {
             val isDozing by collectLastValue(underTest.isDozing)
+            runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     from = KeyguardState.AOD,
@@ -335,7 +347,8 @@
             )
             runCurrent()
 
-            underTest.completeDozeAnimation()
+            assertThat(isDozing?.isAnimating).isEqualTo(true)
+            isDozing?.stopAnimating()
             runCurrent()
 
             assertThat(isDozing?.isAnimating).isEqualTo(false)
@@ -345,6 +358,7 @@
     fun isNotVisible_pulseExpanding() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             notifsKeyguardRepository.setPulseExpanding(true)
             runCurrent()
 
@@ -355,6 +369,7 @@
     fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     to = KeyguardState.GONE,
@@ -364,13 +379,15 @@
             whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
             runCurrent()
 
-            assertThat(isVisible).isEqualTo(AnimatedValue(false, isAnimating = false))
+            assertThat(isVisible?.value).isFalse()
+            assertThat(isVisible?.isAnimating).isFalse()
         }
 
     @Test
     fun isVisible_bypassEnabled() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             deviceEntryRepository.setBypassEnabled(true)
             runCurrent()
 
@@ -381,6 +398,7 @@
     fun isNotVisible_pulseExpanding_notBypassing() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             notifsKeyguardRepository.setPulseExpanding(true)
             deviceEntryRepository.setBypassEnabled(false)
             runCurrent()
@@ -398,26 +416,30 @@
             notifsKeyguardRepository.setNotificationsFullyHidden(true)
             runCurrent()
 
-            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isTrue()
         }
 
     @Test
     fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             notifsKeyguardRepository.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParams.alwaysOn).thenReturn(false)
             notifsKeyguardRepository.setNotificationsFullyHidden(true)
             runCurrent()
 
-            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isFalse()
         }
 
     @Test
     fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             notifsKeyguardRepository.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -425,7 +447,8 @@
             notifsKeyguardRepository.setNotificationsFullyHidden(true)
             runCurrent()
 
-            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isFalse()
         }
 
     @Test
@@ -440,13 +463,15 @@
             notifsKeyguardRepository.setNotificationsFullyHidden(true)
             runCurrent()
 
-            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isTrue()
         }
 
     @Test
     fun isVisible_stopAnimation() =
         scope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
             notifsKeyguardRepository.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -454,7 +479,8 @@
             notifsKeyguardRepository.setNotificationsFullyHidden(true)
             runCurrent()
 
-            underTest.completeVisibilityAnimation()
+            assertThat(isVisible?.isAnimating).isEqualTo(true)
+            isVisible?.stopAnimating()
             runCurrent()
 
             assertThat(isVisible?.isAnimating).isEqualTo(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index e1e7f92..ba68fbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -17,6 +17,8 @@
 
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import android.graphics.Rect
+import android.graphics.drawable.Icon
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.SysUITestModule
@@ -34,13 +36,23 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.plugins.DarkIconDispatcher
 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.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
@@ -61,18 +73,6 @@
     @Mock lateinit var dozeParams: DozeParameters
 
     private lateinit var testComponent: TestComponent
-    private val underTest: NotificationIconContainerStatusBarViewModel
-        get() = testComponent.underTest
-    private val deviceProvisioningRepository
-        get() = testComponent.deviceProvisioningRepository
-    private val keyguardTransitionRepository
-        get() = testComponent.keyguardTransitionRepository
-    private val keyguardRepository
-        get() = testComponent.keyguardRepository
-    private val powerRepository
-        get() = testComponent.powerRepository
-    private val scope
-        get() = testComponent.scope
 
     @Before
     fun setup() {
@@ -82,7 +82,6 @@
             DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
                 .create(
                     test = this,
-                    // Configurable bindings
                     featureFlags =
                         FakeFeatureFlagsClassicModule {
                             set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -93,155 +92,299 @@
                             dozeParameters = dozeParams,
                         ),
                 )
-
-        keyguardRepository.setKeyguardShowing(false)
-        deviceProvisioningRepository.setFactoryResetProtectionActive(false)
-        powerRepository.updateWakefulness(
-            rawState = WakefulnessState.AWAKE,
-            lastWakeReason = WakeSleepReason.OTHER,
-            lastSleepReason = WakeSleepReason.OTHER,
-        )
+                .apply {
+                    keyguardRepository.setKeyguardShowing(false)
+                    deviceProvisioningRepository.setFactoryResetProtectionActive(false)
+                    powerRepository.updateWakefulness(
+                        rawState = WakefulnessState.AWAKE,
+                        lastWakeReason = WakeSleepReason.OTHER,
+                        lastSleepReason = WakeSleepReason.OTHER,
+                    )
+                }
     }
 
     @Test
     fun animationsEnabled_isFalse_whenFrpIsActive() =
-        scope.runTest {
-            deviceProvisioningRepository.setFactoryResetProtectionActive(true)
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
+        with(testComponent) {
+            scope.runTest {
+                deviceProvisioningRepository.setFactoryResetProtectionActive(true)
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                    )
                 )
-            )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
-            runCurrent()
-            assertThat(animationsEnabled).isFalse()
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+                runCurrent()
+                assertThat(animationsEnabled).isFalse()
+            }
         }
 
     @Test
     fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
-        scope.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.ASLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
+        with(testComponent) {
+            scope.runTest {
+                powerRepository.updateWakefulness(
+                    rawState = WakefulnessState.ASLEEP,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.OTHER,
                 )
-            )
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_AOD,
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                    )
                 )
-            )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
-            runCurrent()
-            assertThat(animationsEnabled).isFalse()
+                keyguardRepository.setDozeTransitionModel(
+                    DozeTransitionModel(
+                        to = DozeStateModel.DOZE_AOD,
+                    )
+                )
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+                runCurrent()
+                assertThat(animationsEnabled).isFalse()
+            }
         }
 
     @Test
     fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
-        scope.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.ASLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
+        with(testComponent) {
+            scope.runTest {
+                powerRepository.updateWakefulness(
+                    rawState = WakefulnessState.ASLEEP,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.OTHER,
                 )
-            )
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_PULSING,
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                    )
                 )
-            )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
-            runCurrent()
-            assertThat(animationsEnabled).isTrue()
+                keyguardRepository.setDozeTransitionModel(
+                    DozeTransitionModel(
+                        to = DozeStateModel.DOZE_PULSING,
+                    )
+                )
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+                runCurrent()
+                assertThat(animationsEnabled).isTrue()
+            }
         }
 
     @Test
     fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
-        scope.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.STARTING_TO_SLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    transitionState = TransitionState.STARTED,
+        with(testComponent) {
+            scope.runTest {
+                powerRepository.updateWakefulness(
+                    rawState = WakefulnessState.STARTING_TO_SLEEP,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.OTHER,
                 )
-            )
-            whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
-            runCurrent()
-            assertThat(animationsEnabled).isFalse()
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        from = KeyguardState.GONE,
+                        to = KeyguardState.AOD,
+                        transitionState = TransitionState.STARTED,
+                    )
+                )
+                whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+                runCurrent()
+                assertThat(animationsEnabled).isFalse()
+            }
         }
 
     @Test
     fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
-        scope.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.STARTING_TO_SLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    transitionState = TransitionState.STARTED,
+        with(testComponent) {
+            scope.runTest {
+                powerRepository.updateWakefulness(
+                    rawState = WakefulnessState.STARTING_TO_SLEEP,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.OTHER,
                 )
-            )
-            whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
-            runCurrent()
-            assertThat(animationsEnabled).isTrue()
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        from = KeyguardState.GONE,
+                        to = KeyguardState.AOD,
+                        transitionState = TransitionState.STARTED,
+                    )
+                )
+                whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+                runCurrent()
+                assertThat(animationsEnabled).isTrue()
+            }
         }
 
     @Test
     fun animationsEnabled_isTrue_whenNotAsleep() =
-        scope.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.AWAKE,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
+        with(testComponent) {
+            scope.runTest {
+                powerRepository.updateWakefulness(
+                    rawState = WakefulnessState.AWAKE,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.OTHER,
                 )
-            )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
-            runCurrent()
-            assertThat(animationsEnabled).isTrue()
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                    )
+                )
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+                runCurrent()
+                assertThat(animationsEnabled).isTrue()
+            }
         }
 
     @Test
     fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
-        scope.runTest {
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+        with(testComponent) {
+            scope.runTest {
+                val animationsEnabled by collectLastValue(underTest.animationsEnabled)
 
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                    )
                 )
-            )
-            keyguardRepository.setKeyguardShowing(true)
-            runCurrent()
+                keyguardRepository.setKeyguardShowing(true)
+                runCurrent()
 
-            assertThat(animationsEnabled).isFalse()
+                assertThat(animationsEnabled).isFalse()
 
-            keyguardRepository.setKeyguardShowing(false)
-            runCurrent()
+                keyguardRepository.setKeyguardShowing(false)
+                runCurrent()
 
-            assertThat(animationsEnabled).isTrue()
+                assertThat(animationsEnabled).isTrue()
+            }
+        }
+
+    @Test
+    fun iconColors_testsDarkBounds() =
+        with(testComponent) {
+            scope.runTest {
+                darkIconRepository.darkState.value =
+                    SysuiDarkIconDispatcher.DarkChange(
+                        emptyList(),
+                        0f,
+                        0xAABBCC,
+                    )
+                val iconColorsLookup by collectLastValue(underTest.iconColors)
+                assertThat(iconColorsLookup).isNotNull()
+
+                val iconColors = iconColorsLookup?.iconColors(Rect())
+                assertThat(iconColors).isNotNull()
+                iconColors!!
+
+                assertThat(iconColors.tint).isEqualTo(0xAABBCC)
+
+                val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true)
+
+                assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
+            }
+        }
+
+    @Test
+    fun iconColors_staticDrawableColor_nonColorized() =
+        with(testComponent) {
+            scope.runTest {
+                darkIconRepository.darkState.value =
+                    SysuiDarkIconDispatcher.DarkChange(
+                        emptyList(),
+                        0f,
+                        0xAABBCC,
+                    )
+                val iconColorsLookup by collectLastValue(underTest.iconColors)
+                val iconColors = iconColorsLookup?.iconColors(Rect())
+                val staticDrawableColor =
+                    iconColors?.staticDrawableColor(Rect(), isColorized = false)
+                assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+            }
+        }
+
+    @Test
+    fun iconColors_staticDrawableColor_isColorized_notInDarkTintArea() =
+        with(testComponent) {
+            scope.runTest {
+                darkIconRepository.darkState.value =
+                    SysuiDarkIconDispatcher.DarkChange(
+                        listOf(Rect(0, 0, 5, 5)),
+                        0f,
+                        0xAABBCC,
+                    )
+                val iconColorsLookup by collectLastValue(underTest.iconColors)
+                val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
+                val staticDrawableColor =
+                    iconColors?.staticDrawableColor(Rect(6, 6, 7, 7), isColorized = true)
+                assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+            }
+        }
+
+    @Test
+    fun iconColors_notInDarkTintArea() =
+        with(testComponent) {
+            scope.runTest {
+                darkIconRepository.darkState.value =
+                    SysuiDarkIconDispatcher.DarkChange(
+                        listOf(Rect(0, 0, 5, 5)),
+                        0f,
+                        0xAABBCC,
+                    )
+                val iconColorsLookup by collectLastValue(underTest.iconColors)
+                val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7))
+                assertThat(iconColors).isNull()
+            }
+        }
+
+    @Test
+    fun isolatedIcon_animateOnAppear_shadeCollapsed() =
+        with(testComponent) {
+            scope.runTest {
+                val icon: Icon = mock()
+                shadeRepository.setLegacyShadeExpansion(0f)
+                activeNotificationsRepository.activeNotifications.value =
+                    listOf(
+                            activeNotificationModel(
+                                key = "notif1",
+                                groupKey = "group",
+                                statusBarIcon = icon
+                            )
+                        )
+                        .associateBy { it.key }
+                val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+                runCurrent()
+
+                headsUpViewStateRepository.isolatedNotification.value = "notif1"
+                runCurrent()
+
+                assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+                assertThat(isolatedIcon?.isAnimating).isTrue()
+            }
+        }
+
+    @Test
+    fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
+        with(testComponent) {
+            scope.runTest {
+                val icon: Icon = mock()
+                shadeRepository.setLegacyShadeExpansion(.5f)
+                activeNotificationsRepository.activeNotifications.value =
+                    listOf(
+                            activeNotificationModel(
+                                key = "notif1",
+                                groupKey = "group",
+                                statusBarIcon = icon
+                            )
+                        )
+                        .associateBy { it.key }
+                val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+                runCurrent()
+
+                headsUpViewStateRepository.isolatedNotification.value = "notif1"
+                runCurrent()
+
+                assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+                assertThat(isolatedIcon?.isAnimating).isFalse()
+            }
         }
 
     @SysUISingleton
@@ -249,7 +392,6 @@
         modules =
             [
                 SysUITestModule::class,
-                // Real impls
                 BiometricsDomainLayerModule::class,
                 UserDomainLayerModule::class,
             ]
@@ -258,10 +400,14 @@
 
         val underTest: NotificationIconContainerStatusBarViewModel
 
+        val activeNotificationsRepository: ActiveNotificationListRepository
+        val darkIconRepository: FakeDarkIconRepository
         val deviceProvisioningRepository: FakeDeviceProvisioningRepository
+        val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
         val keyguardTransitionRepository: FakeKeyguardTransitionRepository
         val keyguardRepository: FakeKeyguardRepository
         val powerRepository: FakePowerRepository
+        val shadeRepository: FakeShadeRepository
         val scope: TestScope
 
         @Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 8f39ee6..b922ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -36,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationChannel;
 import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -51,6 +53,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -62,11 +67,15 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.utils.os.FakeHandler;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,9 +86,6 @@
 import java.util.Map;
 import java.util.function.Consumer;
 
-import dagger.BindsInstance;
-import dagger.Component;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -93,7 +99,9 @@
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private UserTracker mUserTracker;
-    private final FakeSettings mFakeSettings = new FakeSettings();
+    private final FakeSettings mSecureSettings = new FakeSettings();
+    private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
 
     private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
     private NotificationEntry mEntry;
@@ -113,8 +121,9 @@
                                 mHighPriorityProvider,
                                 mStatusBarStateController,
                                 mUserTracker,
-                                mFakeSettings,
-                                mFakeSettings);
+                                mSecureSettings,
+                                mGlobalSettings,
+                                mFeatureFlags);
         mKeyguardNotificationVisibilityProvider = component.getProvider();
         for (CoreStartable startable : component.getCoreStartables().values()) {
             startable.start();
@@ -223,7 +232,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         verify(listener).accept(anyString());
     }
@@ -234,7 +243,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true);
 
         verify(listener).accept(anyString());
     }
@@ -242,8 +251,8 @@
     @Test
     public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() {
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         GroupEntry parent = new GroupEntryBuilder()
                 .setKey("parent")
                 .addChild(mEntry)
@@ -264,8 +273,8 @@
     @Test
     public void keyguardShowing_hideSilentNotifications_perUserSetting() {
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
                 .setImportance(IMPORTANCE_LOW)
@@ -277,8 +286,8 @@
     @Test
     public void keyguardShowing_hideSilentNotifications_perUserSetting_withHighPriorityParent() {
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         GroupEntry parent = new GroupEntryBuilder()
                 .setKey("parent")
                 .addChild(mEntry)
@@ -300,10 +309,10 @@
     public void hideSilentOnLockscreenSetting() {
         // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         // WHEN the show silent notifs on lockscreen setting is false
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
 
         // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
@@ -319,10 +328,10 @@
     public void showSilentOnLockscreenSetting() {
         // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         // WHEN the show silent notifs on lockscreen setting is true
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
 
         // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
@@ -338,7 +347,7 @@
     public void defaultSilentOnLockscreenSettingIsHide() {
         // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
@@ -348,7 +357,8 @@
         when(mHighPriorityProvider.isExplicitlyHighPriority(any())).thenReturn(false);
 
         // WhHEN the show silent notifs on lockscreen setting is unset
-        assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS));
+        assertNull(
+                mSecureSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS));
 
         assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
     }
@@ -359,7 +369,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Global.ZEN_MODE, true);
+        mGlobalSettings.putBool(Settings.Global.ZEN_MODE, true);
 
         verify(listener).accept(anyString());
     }
@@ -370,7 +380,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
 
         verify(listener).accept(anyString());
     }
@@ -421,6 +431,7 @@
 
     @Test
     public void publicMode_settingsDisallow() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -430,12 +441,59 @@
         when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
                 .thenReturn(false);
 
+        mEntry.setRanking(new RankingBuilder()
+                .setChannel(new NotificationChannel("1", "1", 4))
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .setKey(mEntry.getKey()).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void publicMode_settingsDisallow_mainThread() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+                .thenReturn(false);
+
+        mEntry.setRanking(new RankingBuilder()
+                .setChannel(new NotificationChannel("1", "1", 4))
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .setKey(mEntry.getKey()).build());
+
         // THEN filter out the entry
         assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
     }
 
     @Test
     public void publicMode_notifDisallowed() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void publicMode_notifDisallowed_mainThread() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -470,8 +528,8 @@
     public void highPriorityCharacteristicsIgnored() {
         // GIVEN an 'unfiltered-keyguard-showing' state with silent notifications hidden
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
 
         // WHEN the notification doesn't exceed the threshold to show on the lockscreen, but does
         // have the "high priority characteristics" that would promote it to high priority
@@ -503,6 +561,54 @@
     }
 
     @Test
+    public void notificationChannelVisibilityNoOverride() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        mEntry = entryBuilder.build();
+        // ranking says secret because of DPC or Setting
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setVisibilityOverride(VISIBILITY_SECRET)
+                .setImportance(IMPORTANCE_HIGH)
+                .build());
+
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void notificationChannelVisibilitySecret() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        // And a VISIBILITY_SECRET NotificationChannel
+        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
+        mEntry = entryBuilder.build();
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+
+        mEntry.setRanking(new RankingBuilder(mEntry.getRanking())
+                .setChannel(channel)
+                .build());
+
+        // THEN hide the entry based on visibility.
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
     public void notificationVisibilityPrivate() {
         // GIVEN a VISIBILITY_PRIVATE notification
         NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
@@ -557,7 +663,7 @@
                 .build());
 
         // WHEN its parent does exceed threshold tot show on the lockscreen
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         when(mHighPriorityProvider.isExplicitlyHighPriority(parent)).thenReturn(true);
 
         // THEN filter out the entry regardless of parent
@@ -632,7 +738,8 @@
                     @BindsInstance SysuiStatusBarStateController statusBarStateController,
                     @BindsInstance UserTracker userTracker,
                     @BindsInstance SecureSettings secureSettings,
-                    @BindsInstance GlobalSettings globalSettings
+                    @BindsInstance GlobalSettings globalSettings,
+                    @BindsInstance FeatureFlagsClassic featureFlags
             );
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index cbb0894..947bcfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -2,7 +2,6 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
@@ -19,7 +18,30 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() {
+class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
+    override val provider: VisualInterruptionDecisionProvider
+        get() =
+            NotificationInterruptStateProviderWrapper(
+                NotificationInterruptStateProviderImpl(
+                        context.contentResolver,
+                        powerManager,
+                        ambientDisplayConfiguration,
+                        batteryController,
+                        statusBarStateController,
+                        keyguardStateController,
+                        headsUpManager,
+                        logger,
+                        mainHandler,
+                        flags,
+                        keyguardNotificationVisibilityProvider,
+                        uiEventLogger,
+                        userTracker,
+                        deviceProvisionedController
+                    )
+                    .also { it.mUseHeadsUp = true }
+            )
+
+    // Tests of internals of the wrapper:
 
     @Test
     fun decisionOfTrue() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
new file mode 100644
index 0000000..6f4bbd5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -0,0 +1,221 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import android.app.ActivityManager
+import android.app.Notification
+import android.app.Notification.BubbleMetadata
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_MUTABLE
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.hardware.display.FakeAmbientDisplayConfiguration
+import android.os.Handler
+import android.os.PowerManager
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.statusbar.FakeStatusBarStateController
+import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.`when` as whenever
+
+abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
+    private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
+
+    protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context)
+    protected val batteryController = FakeBatteryController(leakCheck)
+    protected val deviceProvisionedController: DeviceProvisionedController = mock()
+    protected val flags: NotifPipelineFlags = mock()
+    protected val headsUpManager: HeadsUpManager = mock()
+    protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
+        mock()
+    protected val keyguardStateController: KeyguardStateController = mock()
+    protected val logger: NotificationInterruptLogger = mock()
+    protected val mainHandler: Handler = mock()
+    protected val powerManager: PowerManager = mock()
+    protected val statusBarStateController = FakeStatusBarStateController()
+    protected val uiEventLogger = UiEventLoggerFake()
+    protected val userTracker = FakeUserTracker()
+
+    protected abstract val provider: VisualInterruptionDecisionProvider
+
+    @Before
+    fun setUp() {
+        val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+        userTracker.set(listOf(user), /* currentUserIndex = */ 0)
+
+        whenever(headsUpManager.isSnoozed(any())).thenReturn(false)
+        whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
+            .thenReturn(false)
+    }
+
+    @Test
+    fun testShouldPeek() {
+        ensureStateForPeek()
+
+        assertTrue(provider.makeUnloggedHeadsUpDecision(createPeekEntry()).shouldInterrupt)
+    }
+
+    @Test
+    fun testShouldPulse() {
+        ensureStateForPulse()
+
+        assertTrue(provider.makeUnloggedHeadsUpDecision(createPulseEntry()).shouldInterrupt)
+    }
+
+    @Test
+    fun testShouldFsi_awake() {
+        ensureStateForAwakeFsi()
+
+        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+    }
+
+    @Test
+    fun testShouldFsi_dreaming() {
+        ensureStateForDreamingFsi()
+
+        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+    }
+
+    @Test
+    fun testShouldFsi_keyguard() {
+        ensureStateForKeyguardFsi()
+
+        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+    }
+
+    @Test
+    fun testShouldBubble() {
+        assertTrue(provider.makeAndLogBubbleDecision(createBubbleEntry()).shouldInterrupt)
+    }
+
+    private fun ensureStateForPeek() {
+        whenever(powerManager.isScreenOn).thenReturn(true)
+        statusBarStateController.dozing = false
+        statusBarStateController.dreaming = false
+    }
+
+    private fun ensureStateForPulse() {
+        ambientDisplayConfiguration.fakePulseOnNotificationEnabled = true
+        batteryController.setIsAodPowerSave(false)
+        statusBarStateController.dozing = true
+    }
+
+    private fun ensureStateForAwakeFsi() {
+        whenever(powerManager.isInteractive).thenReturn(false)
+        statusBarStateController.dreaming = false
+        statusBarStateController.state = SHADE
+    }
+
+    private fun ensureStateForDreamingFsi() {
+        whenever(powerManager.isInteractive).thenReturn(true)
+        statusBarStateController.dreaming = true
+        statusBarStateController.state = SHADE
+    }
+
+    private fun ensureStateForKeyguardFsi() {
+        whenever(powerManager.isInteractive).thenReturn(true)
+        statusBarStateController.dreaming = false
+        statusBarStateController.state = KEYGUARD
+    }
+
+    private fun createNotif(
+        hasFsi: Boolean = false,
+        bubbleMetadata: BubbleMetadata? = null
+    ): Notification {
+        return Notification.Builder(context, TEST_CHANNEL_ID)
+            .apply {
+                setContentTitle(TEST_CONTENT_TITLE)
+                setContentText(TEST_CONTENT_TEXT)
+
+                if (hasFsi) {
+                    setFullScreenIntent(mock(), /* highPriority = */ true)
+                }
+
+                if (bubbleMetadata != null) {
+                    setBubbleMetadata(bubbleMetadata)
+                }
+            }
+            .setContentTitle(TEST_CONTENT_TITLE)
+            .setContentText(TEST_CONTENT_TEXT)
+            .build()
+    }
+
+    private fun createBubbleMetadata(): BubbleMetadata {
+        val pendingIntent =
+            PendingIntent.getActivity(
+                context,
+                /* requestCode = */ 0,
+                Intent().setPackage(context.packageName),
+                FLAG_MUTABLE
+            )
+
+        val icon = Icon.createWithResource(context.resources, R.drawable.android)
+
+        return BubbleMetadata.Builder(pendingIntent, icon).build()
+    }
+
+    private fun createEntry(
+        notif: Notification,
+        importance: Int = IMPORTANCE_DEFAULT,
+        canBubble: Boolean? = null
+    ): NotificationEntry {
+        return NotificationEntryBuilder()
+            .apply {
+                setPkg(TEST_PACKAGE)
+                setOpPkg(TEST_PACKAGE)
+                setTag(TEST_TAG)
+                setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
+                setNotification(notif)
+                setImportance(importance)
+
+                if (canBubble != null) {
+                    setCanBubble(canBubble)
+                }
+            }
+            .build()
+    }
+
+    private fun createPeekEntry() = createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH)
+
+    private fun createPulseEntry() =
+        createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH).also {
+            modifyRanking(it).setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()
+        }
+
+    private fun createFsiEntry() =
+        createEntry(notif = createNotif(hasFsi = true), importance = IMPORTANCE_HIGH)
+
+    private fun createBubbleEntry() =
+        createEntry(
+            notif = createNotif(bubbleMetadata = createBubbleMetadata()),
+            importance = IMPORTANCE_HIGH,
+            canBubble = true
+        )
+}
+
+private const val TEST_CONTENT_TITLE = "Test Content Title"
+private const val TEST_CONTENT_TEXT = "Test content text"
+private const val TEST_CHANNEL_ID = "test_channel"
+private const val TEST_CHANNEL_NAME = "Test Channel"
+private const val TEST_PACKAGE = "test_package"
+private const val TEST_TAG = "test_tag"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index a0f5048..4bb28ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -50,8 +50,8 @@
 
     @Test
     fun testViewWalker_plainNotification_withPublicView() {
-        val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
-        val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+        val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
         testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
         val row =
             testHelper.createRow(
@@ -122,9 +122,9 @@
 
     @Test
     fun testViewWalker_bigPictureNotification() {
-        val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
-        val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
-        val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+        val bigPicture = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+        val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888))
         val row =
             testHelper.createRow(
                 Notification.Builder(mContext)
@@ -182,8 +182,8 @@
 
     @Test
     fun testViewWalker_customView() {
-        val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
-        val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+        val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
 
         val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
         views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
deleted file mode 100644
index 47c5e5b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.logging
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogcatEchoTracker
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.statusbar.notification.stack.StackStateLogger
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class StackStateLoggerTest : SysuiTestCase() {
-    private val logBufferCounter = LogBufferCounter()
-    private lateinit var logger: StackStateLogger
-
-    @Before
-    fun setup() {
-        logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer)
-    }
-
-    @Test
-    fun groupChildRemovalEvent() {
-        logger.groupChildRemovalEventProcessed(KEY)
-        verifyDidLog(1)
-        logger.groupChildRemovalAnimationEnded(KEY)
-        verifyDidLog(1)
-    }
-
-    class LogBufferCounter {
-        val recentLogs = mutableListOf<Pair<String, LogLevel>>()
-        val tracker =
-            object : LogcatEchoTracker {
-                override val logInBackgroundThread: Boolean = false
-                override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false
-                override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
-                    recentLogs.add(tagName to level)
-                    return true
-                }
-            }
-        val logBuffer =
-            LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false)
-
-        fun verifyDidLog(times: Int) {
-            Truth.assertThat(recentLogs).hasSize(times)
-            recentLogs.clear()
-        }
-    }
-
-    private fun verifyDidLog(times: Int) {
-        logBufferCounter.verifyDidLog(times)
-    }
-
-    companion object {
-        private val KEY = "PACKAGE_NAME"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
index 23ae26c..1bb7b61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -24,9 +24,9 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.NotificationDrawableConsumer
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -45,6 +45,7 @@
 import org.mockito.Mockito.verifyZeroInteractions
 
 private const val FREE_IMAGE_DELAY_MS = 4000L
+private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -81,6 +82,7 @@
     @Before
     fun setUp() {
         allowTestableLooperAsMainThread()
+        overrideMaxImageSizes()
         iconManager =
             BigPictureIconManager(
                 context,
@@ -430,6 +432,17 @@
             verifyZeroInteractions(mockConsumer)
         }
 
+    private fun overrideMaxImageSizes() {
+        testableResources.addOverride(
+            com.android.internal.R.dimen.notification_big_picture_max_width,
+            MAX_IMAGE_SIZE
+        )
+        testableResources.addOverride(
+            com.android.internal.R.dimen.notification_big_picture_max_height,
+            MAX_IMAGE_SIZE
+        )
+    }
+
     private fun assertIsPlaceHolder(drawable: Drawable) {
         assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
index 8c3bfd5..f7632aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
@@ -30,9 +30,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.TestUiOffloadThread
+import com.android.systemui.UiOffloadThread
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler
+import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -60,6 +63,12 @@
     fun setUp() {
         looper = TestableLooper.get(this)
         allowTestableLooperAsMainThread()
+        // Use main thread instead of UI offload thread to fix flakes.
+        mDependency.injectTestDependency(
+            UiOffloadThread::class.java,
+            TestUiOffloadThread(looper.looper)
+        )
+
         helper = NotificationTestHelper(mContext, mDependency, looper)
         row = helper.createRow()
         // Some code in the view iterates through parents so we need some extra containers around
@@ -88,12 +97,11 @@
         val action2 = createActionWithPendingIntent()
         val action3 = createActionWithPendingIntent()
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread() // Wait for cancellation registration to execute.
 
         val pi3 = getPendingIntent(action3)
         pi3.cancel()
-        looper.processAllMessages() // Wait for listener callbacks to execute
 
+        waitForActionDisabled(action3)
         assertThat(action1.isEnabled).isTrue()
         assertThat(action2.isEnabled).isTrue()
         assertThat(action3.isEnabled).isFalse()
@@ -109,12 +117,12 @@
         val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
         val action = createActionWithPendingIntent()
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread() // Wait for cancellation registration to execute.
 
         // Cancel the intent and check action is now false.
         val pi = getPendingIntent(action)
         pi.cancel()
-        looper.processAllMessages() // Wait for listener callbacks to execute
+
+        waitForActionDisabled(action)
         assertThat(action.isEnabled).isFalse()
 
         // Create a NEW action and make sure that one will also be cancelled with same PI.
@@ -134,12 +142,13 @@
         val action2 = createActionWithPendingIntent()
         val action3 = createActionWithPendingIntent(getPendingIntent(action2))
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread() // Wait for cancellation registration to execute.
+        looper.processAllMessages()
 
         val pi = getPendingIntent(action2)
         pi.cancel()
-        looper.processAllMessages() // Wait for listener callbacks to execute
 
+        waitForActionDisabled(action2)
+        waitForActionDisabled(action3)
         assertThat(action1.isEnabled).isTrue()
         assertThat(action2.isEnabled).isFalse()
         assertThat(action3.isEnabled).isFalse()
@@ -152,10 +161,12 @@
         val action = createActionWithPendingIntent()
         wrapper.onContentUpdated(row)
         getPendingIntent(action).cancel()
-        ViewUtils.attachView(root)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
+        ViewUtils.attachView(root)
+        looper.processAllMessages()
+
+        waitForActionDisabled(action)
         assertThat(action.isEnabled).isFalse()
     }
 
@@ -173,7 +184,6 @@
         val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
         wrapper.onContentUpdated(row)
         ViewUtils.detachView(root)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
         val captor = ArgumentCaptor.forClass(CancelListener::class.java)
@@ -194,7 +204,6 @@
         val action = createActionWithPendingIntent(spy)
         val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
         // Grab set attach listener
@@ -213,7 +222,6 @@
             )
         action.setTagInternal(R.id.pending_intent_tag, newPi)
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
         // Listeners for original pending intent need to be cleaned up now.
@@ -251,4 +259,11 @@
         assertThat(pendingIntent).isNotNull()
         return pendingIntent
     }
+
+    private fun waitForActionDisabled(action: View) {
+        waitForCondition {
+            looper.processAllMessages()
+            !action.isEnabled
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
new file mode 100644
index 0000000..ca105f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.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.shared
+
+import android.graphics.drawable.Icon
+import com.google.common.truth.Correspondence
+
+val byKey: Correspondence<ActiveNotificationModel, String> =
+    Correspondence.transforming({ it?.key }, "has a key of")
+val byIsAmbient: Correspondence<ActiveNotificationModel, Boolean> =
+    Correspondence.transforming({ it?.isAmbient }, "has an isAmbient value of")
+val byIsSuppressedFromStatusBar: Correspondence<ActiveNotificationModel, Boolean> =
+    Correspondence.transforming(
+        { it?.isSuppressedFromStatusBar },
+        "has an isSuppressedFromStatusBar value of",
+    )
+val byIsSilent: Correspondence<ActiveNotificationModel, Boolean> =
+    Correspondence.transforming({ it?.isSilent }, "has an isSilent value of")
+val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> =
+    Correspondence.transforming({ it?.isRowDismissed }, "has an isRowDismissed value of")
+val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> =
+    Correspondence.transforming(
+        { it?.isLastMessageFromReply },
+        "has an isLastMessageFromReply value of"
+    )
+val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
+    Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
+
+fun activeNotificationModel(
+    key: String,
+    groupKey: String? = null,
+    isAmbient: Boolean = false,
+    isRowDismissed: Boolean = false,
+    isSilent: Boolean = false,
+    isLastMessageFromReply: Boolean = false,
+    isSuppressedFromStatusBar: Boolean = false,
+    isPulsing: Boolean = false,
+    aodIcon: Icon? = null,
+    shelfIcon: Icon? = null,
+    statusBarIcon: Icon? = null,
+) =
+    ActiveNotificationModel(
+        key = key,
+        groupKey = groupKey,
+        isAmbient = isAmbient,
+        isRowDismissed = isRowDismissed,
+        isSilent = isSilent,
+        isLastMessageFromReply = isLastMessageFromReply,
+        isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+        isPulsing = isPulsing,
+        aodIcon = aodIcon,
+        shelfIcon = shelfIcon,
+        statusBarIcon = statusBarIcon,
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index 390c1dd..02a67d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -21,23 +21,25 @@
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
-import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
-import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
@@ -55,98 +57,118 @@
 
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
-    // mocks
     @Mock private lateinit var keyguardTransitionController: LockscreenShadeTransitionController
     @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
-    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
 
-    // fakes
-    private val keyguardRepository = FakeKeyguardRepository()
-    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val a11yRepo = FakeAccessibilityRepository()
-    private val powerRepository = FakePowerRepository()
-    private val powerInteractor by lazy {
-        PowerInteractorFactory.create(
-                repository = powerRepository,
-                screenOffAnimationController = screenOffAnimationController,
-                statusBarStateController = statusBarStateController,
-            )
-            .powerInteractor
-    }
-
-    // real impls
-    private val a11yInteractor = AccessibilityInteractor(a11yRepo)
-    private val activatableViewModel = ActivatableNotificationViewModel(a11yInteractor)
-    private val interactor by lazy {
-        NotificationShelfInteractor(
-            keyguardRepository,
-            deviceEntryFaceAuthRepository,
-            powerInteractor,
-            keyguardTransitionController,
-        )
-    }
-    private val underTest by lazy { NotificationShelfViewModel(interactor, activatableViewModel) }
+    private lateinit var testComponent: TestComponent
 
     @Before
     fun setUp() {
         whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+        testComponent =
+            DaggerNotificationShelfViewModelTest_TestComponent.factory()
+                .create(
+                    test = this,
+                    mocks =
+                        TestMocksModule(
+                            lockscreenShadeTransitionController = keyguardTransitionController,
+                            screenOffAnimationController = screenOffAnimationController,
+                            statusBarStateController = statusBarStateController,
+                        )
+                )
     }
 
     @Test
-    fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
-        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+    fun canModifyColorOfNotifications_whenKeyguardNotShowing() =
+        with(testComponent) {
+            testScope.runTest {
+                val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
 
-        keyguardRepository.setKeyguardShowing(false)
+                keyguardRepository.setKeyguardShowing(false)
 
-        assertThat(canModifyNotifColor).isTrue()
-    }
+                assertThat(canModifyNotifColor).isTrue()
+            }
+        }
 
     @Test
-    fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
-        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+    fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() =
+        with(testComponent) {
+            testScope.runTest {
+                val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
 
-        keyguardRepository.setKeyguardShowing(true)
-        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+                keyguardRepository.setKeyguardShowing(true)
+                deviceEntryFaceAuthRepository.isBypassEnabled.value = false
 
-        assertThat(canModifyNotifColor).isTrue()
-    }
+                assertThat(canModifyNotifColor).isTrue()
+            }
+        }
 
     @Test
-    fun cannotModifyColorOfNotifications_whenBypass() = runTest {
-        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+    fun cannotModifyColorOfNotifications_whenBypass() =
+        with(testComponent) {
+            testScope.runTest {
+                val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
 
-        keyguardRepository.setKeyguardShowing(true)
-        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+                keyguardRepository.setKeyguardShowing(true)
+                deviceEntryFaceAuthRepository.isBypassEnabled.value = true
 
-        assertThat(canModifyNotifColor).isFalse()
-    }
+                assertThat(canModifyNotifColor).isFalse()
+            }
+        }
 
     @Test
-    fun isClickable_whenKeyguardShowing() = runTest {
-        val isClickable by collectLastValue(underTest.isClickable)
+    fun isClickable_whenKeyguardShowing() =
+        with(testComponent) {
+            testScope.runTest {
+                val isClickable by collectLastValue(underTest.isClickable)
 
-        keyguardRepository.setKeyguardShowing(true)
+                keyguardRepository.setKeyguardShowing(true)
 
-        assertThat(isClickable).isTrue()
-    }
+                assertThat(isClickable).isTrue()
+            }
+        }
 
     @Test
-    fun isNotClickable_whenKeyguardNotShowing() = runTest {
-        val isClickable by collectLastValue(underTest.isClickable)
+    fun isNotClickable_whenKeyguardNotShowing() =
+        with(testComponent) {
+            testScope.runTest {
+                val isClickable by collectLastValue(underTest.isClickable)
 
-        keyguardRepository.setKeyguardShowing(false)
+                keyguardRepository.setKeyguardShowing(false)
 
-        assertThat(isClickable).isFalse()
-    }
+                assertThat(isClickable).isFalse()
+            }
+        }
 
     @Test
-    fun onClicked_goesToLockedShade() {
-        whenever(statusBarStateController.isDozing).thenReturn(true)
+    fun onClicked_goesToLockedShade() =
+        with(testComponent) {
+            whenever(statusBarStateController.isDozing).thenReturn(true)
 
-        underTest.onShelfClicked()
+            underTest.onShelfClicked()
 
-        assertThat(powerRepository.lastWakeReason).isNotNull()
-        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
-        verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
+            assertThat(powerRepository.lastWakeReason).isNotNull()
+            assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+            verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
+        }
+
+    @Component(modules = [SysUITestModule::class, ActivatableNotificationViewModelModule::class])
+    @SysUISingleton
+    interface TestComponent {
+
+        val underTest: NotificationShelfViewModel
+        val deviceEntryFaceAuthRepository: FakeDeviceEntryFaceAuthRepository
+        val keyguardRepository: FakeKeyguardRepository
+        val powerRepository: FakePowerRepository
+        val testScope: TestScope
+
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+                mocks: TestMocksModule,
+            ): TestComponent
+        }
     }
 }
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 20197e3..39e3d5da3 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
@@ -53,6 +53,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.ConfigurationState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -82,13 +83,14 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
 import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -171,8 +173,8 @@
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
-    private final NotificationListInteractor mNotificationListInteractor =
-            new NotificationListInteractor(new NotificationListRepository());
+    private final SeenNotificationsInteractor mSeenNotificationsInteractor =
+            new SeenNotificationsInteractor(new ActiveNotificationListRepository());
 
     private NotificationStackScrollLayoutController mController;
 
@@ -504,7 +506,7 @@
     @Test
     public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
         initController(/* viewIsAttached= */ true);
-        mNotificationListInteractor.setHasFilteredOutSeenNotifications(true);
+        mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
         mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
         verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
         verify(mNotificationStackScrollLayout).updateFooter();
@@ -704,7 +706,7 @@
                 mUiEventLogger,
                 mRemoteInputManager,
                 mVisibilityLocationProviderDelegator,
-                mNotificationListInteractor,
+                mSeenNotificationsInteractor,
                 mShadeController,
                 mJankMonitor,
                 mStackLogger,
@@ -716,8 +718,9 @@
                 mSecureSettings,
                 mock(NotificationDismissibilityProvider.class),
                 mActivityStarter,
-                new ResourcesSplitShadeStateController()
-        );
+                new ResourcesSplitShadeStateController(),
+                mock(ConfigurationState.class),
+                mock(ShelfNotificationIconViewStore.class));
     }
 
     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 a2be8b0..8f36d4f 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
@@ -19,6 +19,8 @@
 import static android.view.View.GONE;
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.systemui.Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
 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;
@@ -164,6 +166,12 @@
         mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
         mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION);
+        mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
+        // Some tests in this file test the FooterView. Since we're refactoring the FooterView
+        // business logic out of the NSSL, the behavior tested in this file will eventually be
+        // tested directly in the new FooterView stack. For now, we just want to make sure that the
+        // old behavior is preserved when the flag is off.
+        setFlagDefault(mSetFlagsRule, FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
 
         // Inject dependencies before initializing the layout
         mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 49906dc..08ef477 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -62,6 +62,10 @@
         )
 
     private val testableResources = mContext.getOrCreateTestableResources()
+    private val maxPanelHeight =
+        mContext.resources.displayMetrics.heightPixels -
+                px(R.dimen.notification_panel_margin_top) -
+                px(R.dimen.notification_panel_margin_bottom)
 
     private fun px(@DimenRes id: Int): Float =
             testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -147,7 +151,7 @@
         stackScrollAlgorithm.initView(context)
         hostView.removeAllViews()
         hostView.addView(emptyShadeView)
-        ambientState.layoutMaxHeight = 1280
+        ambientState.layoutMaxHeight = maxPanelHeight.toInt()
 
         stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
 
@@ -155,7 +159,7 @@
                 context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
         val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
         val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
-        assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
+        assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
     }
 
     @Test
@@ -356,7 +360,7 @@
         whenever(notificationRow.canViewBeCleared()).thenReturn(false)
         ambientState.isClearAllInProgress = true
         ambientState.isShadeExpanded = true
-        ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+        ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
         hostView.addView(footerView)
 
         stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -370,7 +374,7 @@
         whenever(notificationRow.canViewBeCleared()).thenReturn(true)
         ambientState.isClearAllInProgress = true
         ambientState.isShadeExpanded = true
-        ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+        ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
         hostView.addView(footerView)
 
         stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -382,7 +386,7 @@
     fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() {
         ambientState.isClearAllInProgress = true
         ambientState.isShadeExpanded = true
-        ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+        ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
         hostView.removeAllViews() // remove all rows
         hostView.addView(footerView)
 
@@ -1006,7 +1010,7 @@
     }
 
     private fun resetViewStates_stackMargin_changesHunYTranslation() {
-        val stackTopMargin = 50
+        val stackTopMargin = bigGap.toInt() // a gap smaller than the headsUpInset
         val headsUpTranslationY = stackScrollAlgorithm.mHeadsUpInset - stackTopMargin
 
         // we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
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 ac11ff2..0a7dc4e 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
@@ -192,10 +192,26 @@
             val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
 
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                )
             )
             assertThat(isOnLockscreen).isFalse()
 
+            // While progressing from lockscreen, should still be true
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.8f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            assertThat(isOnLockscreen).isTrue()
+
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 68f2728..7de05ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -19,11 +19,14 @@
 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
@@ -41,6 +44,7 @@
 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
@@ -49,6 +53,7 @@
 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
@@ -116,16 +121,51 @@
     @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)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 416694b..1d8a346 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -50,15 +50,15 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -288,7 +288,7 @@
         inOrderSafety.verify(mSafetyController).removeCallback(any());
         inOrderSafety.verify(mSafetyController).addCallback(any());
 
-        SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
         assertEquals(USER + 1, setting.getCurrentUser());
         assertTrue(setting.isListening());
     }
@@ -342,7 +342,7 @@
         inOrderSafety.verify(mSafetyController).removeCallback(any());
         inOrderSafety.verify(mSafetyController).addCallback(any());
 
-        SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
         assertEquals(USER + 1, setting.getCurrentUser());
         assertFalse(setting.isListening());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 700de53..164325a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -18,7 +18,9 @@
 
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -38,7 +40,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
-import android.view.HapticFeedbackConstants;
 import android.view.ViewRootImpl;
 
 import com.android.internal.logging.MetricsLogger;
@@ -47,17 +48,19 @@
 import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -107,8 +110,6 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
-    private ScreenLifecycle mScreenLifecycle;
-    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private SessionTracker mSessionTracker;
@@ -122,6 +123,12 @@
     private BiometricUnlockLogger mLogger;
     @Mock
     private ViewRootImpl mViewRootImpl;
+    @Mock
+    private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
+    private BiometricUnlockInteractor mBiometricUnlockInteractor;
     private final FakeSystemClock mSystemClock = new FakeSystemClock();
     private FakeFeatureFlags mFeatureFlags;
     private BiometricUnlockController mBiometricUnlockController;
@@ -133,7 +140,7 @@
         mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
-        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
+        when(mKeyguardStateController.isFaceEnrolled()).thenReturn(true);
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
         when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
                 .thenReturn(true);
@@ -158,7 +165,10 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
                 mSystemClock,
-                mFeatureFlags
+                mFeatureFlags,
+                mDeviceEntryHapticsInteractor,
+                () -> mSelectedUserInteractor,
+                mBiometricUnlockInteractor
         );
         biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         biometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -462,145 +472,23 @@
     }
 
     @Test
-    public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
-        // GIVEN side fingerprint enrolled, last wake reason was power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
-        // GIVEN last wake time just occurred
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+    public void onFingerprintSuccess_requestSuccessHaptic() {
         // WHEN biometric fingerprint succeeds
         givenFingerprintModeUnlockCollapsing();
         mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
                 true);
 
-        // THEN DO NOT vibrate the device
-        verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString());
+        // THEN always vibrate the device
+        verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
     }
 
     @Test
-    public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
-        // GIVEN side fingerprint enrolled, last wake reason was power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
-        // GIVEN last wake time was 500ms ago
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-        mSystemClock.advanceTime(500);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).vibrateAuthSuccess(anyString());
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() {
-        // GIVEN oneway haptics is enabled
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        // GIVEN side fingerprint enrolled, last wake reason was power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
-        // GIVEN last wake time was 500ms ago
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-        mSystemClock.advanceTime(500);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).performHapticFeedback(
-                any(),
-                eq(HapticFeedbackConstants.CONFIRM)
-        );
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() {
-        // GIVEN side fingerprint enrolled, wakeup just happened
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
-        // GIVEN last wake reason was from a gesture
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).vibrateAuthSuccess(anyString());
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() {
-        //GIVEN oneway haptics is enabled
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        // GIVEN side fingerprint enrolled, wakeup just happened
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
-        // GIVEN last wake reason was from a gesture
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).performHapticFeedback(
-                any(),
-                eq(HapticFeedbackConstants.CONFIRM)
-        );
-    }
-
-    @Test
-    public void onSideFingerprintFail_alwaysPlaysHaptic() {
-        // GIVEN side fingerprint enrolled, last wake reason was recent power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+    public void onFingerprintFail_requestErrorHaptic() {
         // WHEN biometric fingerprint fails
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN always vibrate the device
-        verify(mVibratorHelper).vibrateAuthError(anyString());
-    }
-
-    @Test
-    public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() {
-        // GIVEN oneway haptics is enabled
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        // GIVEN side fingerprint enrolled, last wake reason was recent power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
-        // WHEN biometric fingerprint fails
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
-        // THEN always vibrate the device
-        verify(mVibratorHelper).performHapticFeedback(
-                any(),
-                eq(HapticFeedbackConstants.REJECT)
-        );
+        verify(mDeviceEntryHapticsInteractor).vibrateError();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f18af61..6478a3e 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
@@ -18,7 +18,10 @@
 
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
@@ -156,12 +159,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
@@ -184,8 +187,6 @@
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
-import dagger.Lazy;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -199,6 +200,8 @@
 
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -275,8 +278,6 @@
             mNotificationShadeWindowViewControllerLazy;
     @Mock private NotificationShelfController mNotificationShelfController;
     @Mock private DozeParameters mDozeParameters;
-    @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
-    @Mock private LockscreenWallpaper mLockscreenWallpaper;
     @Mock private DozeServiceHost mDozeServiceHost;
     @Mock private BackActionInteractor mBackActionInteractor;
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@@ -336,7 +337,7 @@
         mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
         // Set default value to avoid IllegalStateException.
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+        setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
         // For the Shade to respond to Back gesture, we must enable the event routing
         mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
         // For the Shade to animate during the Back gesture, we must enable the animation flag.
@@ -402,7 +403,6 @@
         when(mGradientColors.supportsDarkText()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
         when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
         when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
         when(mNotificationShadeWindowViewControllerLazy.get())
@@ -503,10 +503,8 @@
                 (Lazy<NotificationPresenter>) () -> mNotificationPresenter,
                 (Lazy<NotificationActivityStarter>) () -> mNotificationActivityStarter,
                 mNotifLaunchAnimControllerProvider,
-                new NotificationExpansionRepository(),
                 mDozeParameters,
                 mScrimController,
-                mLockscreenWallpaperLazy,
                 mBiometricUnlockControllerLazy,
                 mAuthRippleController,
                 mDozeServiceHost,
@@ -1110,6 +1108,16 @@
         // THEN no NPE when fingerprintManager is null
     }
 
+    @Test
+    public void bubbleBarVisibility() {
+        createCentralSurfaces();
+        mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+        verify(mBubbles).onStatusBarVisibilityChanged(false);
+
+        mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+        verify(mBubbles).onStatusBarVisibilityChanged(true);
+    }
+
     /**
      * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
      * to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index f5b7ca8..6fecbb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -44,6 +43,7 @@
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -85,6 +85,7 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private UserTracker mUserTracker;
+    @Mock private DozeInteractor mDozeInteractor;
     @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
 
     /**
@@ -128,7 +129,8 @@
             mKeyguardUpdateMonitor,
             mConfigurationController,
             mStatusBarStateController,
-            mUserTracker
+            mUserTracker,
+            mDozeInteractor
         );
 
         verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
@@ -186,9 +188,7 @@
 
     @Test
     public void testGetAlwaysOn_whenBatterySaverCallback() {
-        DozeParameters.Callback callback = mock(DozeParameters.Callback.class);
-        mDozeParameters.addCallback(callback);
-
+        reset(mDozeInteractor);
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mBatteryController.isAodPowerSave()).thenReturn(true);
 
@@ -196,16 +196,16 @@
         mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
         mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
 
-        verify(callback, times(2)).onAlwaysOnChange();
+        verify(mDozeInteractor, times(2)).setAodAvailable(anyBoolean());
         verify(mScreenOffAnimationController, times(2)).onAlwaysOnChanged(false);
         assertThat(mDozeParameters.getAlwaysOn()).isFalse();
 
         reset(mScreenOffAnimationController);
-        reset(callback);
+        reset(mDozeInteractor);
         when(mBatteryController.isAodPowerSave()).thenReturn(false);
         mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
 
-        verify(callback).onAlwaysOnChange();
+        verify(mDozeInteractor).setAodAvailable(anyBoolean());
         verify(mScreenOffAnimationController).onAlwaysOnChanged(true);
         assertThat(mDozeParameters.getAlwaysOn()).isTrue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 593c587..19215e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -42,6 +44,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -51,6 +54,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -96,18 +100,21 @@
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private AuthController mAuthController;
     @Mock private DozeHost.Callback mCallback;
-
     @Mock private DozeInteractor mDozeInteractor;
+
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
         mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
-                mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
-                mBatteryController, mScrimController, () -> mBiometricUnlockController,
-                () -> mAssistManager, mDozeScrimController,
-                mKeyguardUpdateMonitor, mPulseExpansionHandler,
-                mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mAuthController, mNotificationIconAreaController, mDozeInteractor);
+                mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags,
+                mHeadsUpManager, mBatteryController, mScrimController,
+                () -> mBiometricUnlockController, () -> mAssistManager, mDozeScrimController,
+                mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController,
+                mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController,
+                mDozeInteractor);
 
         mDozeServiceHost.initialize(
                 mCentralSurfaces,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index d84bb72..1fad2a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -34,6 +36,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeHeadsUpTracker;
@@ -42,8 +45,10 @@
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.Clock;
@@ -82,10 +87,12 @@
     private KeyguardStateController mKeyguardStateController;
     private CommandQueue mCommandQueue;
     private NotificationRoundnessManager mNotificationRoundnessManager;
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
 
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
+        setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
         mTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
@@ -119,6 +126,8 @@
                 mNotificationRoundnessManager,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
+                mFeatureFlags,
+                mock(HeadsUpNotificationIconInteractor.class),
                 Optional.of(mOperatorNameView));
         mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
     }
@@ -203,6 +212,7 @@
                 mNotificationRoundnessManager,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
+                mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class),
                 Optional.empty());
 
         assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
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 cda2a74..48b95d4 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
@@ -34,7 +34,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+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;
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import org.junit.After;
 import org.junit.Before;
@@ -56,6 +57,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import kotlinx.coroutines.flow.StateFlowKt;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -70,8 +73,9 @@
     @Mock private KeyguardBypassController mBypassController;
     @Mock private ConfigurationControllerImpl mConfigurationController;
     @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
-    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private UiEventLogger mUiEventLogger;
+    @Mock private JavaAdapter mJavaAdapter;
+    @Mock private ShadeInteractor mShadeInteractor;
 
     private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
         TestableHeadsUpManagerPhone(
@@ -85,7 +89,8 @@
                 Handler handler,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger,
-                ShadeExpansionStateManager shadeExpansionStateManager
+                JavaAdapter javaAdapter,
+                ShadeInteractor shadeInteractor
         ) {
             super(
                     context,
@@ -98,7 +103,8 @@
                     handler,
                     accessibilityManagerWrapper,
                     uiEventLogger,
-                    shadeExpansionStateManager
+                    javaAdapter,
+                    shadeInteractor
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
@@ -117,7 +123,8 @@
                 mTestHandler,
                 mAccessibilityManagerWrapper,
                 mUiEventLogger,
-                mShadeExpansionStateManager
+                mJavaAdapter,
+                mShadeInteractor
         );
     }
 
@@ -129,6 +136,7 @@
     @Before
     @Override
     public void setUp() {
+        when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
         final AccessibilityManagerWrapper accessibilityMgr =
                 mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
         when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
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 6209f73..bd0dbee 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
@@ -86,7 +86,7 @@
         featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
         whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true)
-        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardStateController.isFaceEnrolled).thenReturn(true)
     }
 
     @After
@@ -158,7 +158,7 @@
         keyguardBypassController.registerOnBypassStateChangedListener(bypassListener)
         verify(keyguardStateController).addCallback(callback.capture())
 
-        callback.value.onFaceAuthEnabledChanged()
+        callback.value.onFaceEnrolledChanged()
         verify(bypassListener).onBypassStateChanged(anyBoolean())
     }
 
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 03f5f00..3556703 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,9 +30,11 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 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 org.junit.After;
 import org.junit.Before;
@@ -51,8 +53,7 @@
     private static final float OPAQUE = 1.f;
     private static final float TRANSPARENT = 0.f;
 
-    @Mock
-    private Resources mResources;
+    @Mock private Resources mResources;
 
     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private KeyguardClockPositionAlgorithm.Result mClockPosition;
@@ -80,7 +81,8 @@
                 .mockStatic(BurnInHelperKt.class)
                 .startMocking();
 
-        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+        LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
+        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);
         when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
         mClockPositionAlgorithm.loadDimens(mResources);
 
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 6484389..361df1c 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
@@ -168,7 +168,6 @@
                 PowerInteractorFactory.create().getPowerInteractor(),
                 mFeatureFlags,
                 mSceneTestUtils.getSceneContainerFlags(),
-                mSceneTestUtils.getDeviceEntryRepository(),
                 new FakeKeyguardBouncerRepository(),
                 new FakeConfigurationRepository(),
                 new FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index 1b8cfd4..92e40df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
@@ -28,12 +30,14 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.SetFlagsRuleExtensionsKt;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -82,6 +86,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
         mController = new LegacyNotificationIconAreaControllerImpl(
                 mContext,
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
deleted file mode 100644
index 47671fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
+++ /dev/null
@@ -1,101 +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.WallpaperManager
-import android.content.pm.UserInfo
-import android.os.Looper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.os.FakeHandler
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class LockscreenWallpaperTest : SysuiTestCase() {
-
-    private lateinit var underTest: LockscreenWallpaper
-
-    private val testScope = TestScope(StandardTestDispatcher())
-    private val userRepository = FakeUserRepository()
-
-    private val wallpaperManager: WallpaperManager = mock()
-
-    @Before
-    fun setUp() {
-        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
-        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
-        underTest =
-            LockscreenWallpaper(
-                /* wallpaperManager= */ wallpaperManager,
-                /* iWallpaperManager= */ mock(),
-                /* keyguardUpdateMonitor= */ mock(),
-                /* dumpManager= */ mock(),
-                /* mediaManager= */ mock(),
-                /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
-                /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
-                /* userRepository= */ userRepository,
-                /* userTracker= */ mock(),
-            )
-        underTest.start()
-    }
-
-    @Test
-    fun getBitmap_matchesUserIdFromUserRepo() =
-        testScope.runTest {
-            val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
-            userRepository.setUserInfos(listOf(info))
-            userRepository.setSelectedUserInfo(info)
-
-            underTest.bitmap
-
-            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
-        }
-
-    @Test
-    fun getBitmap_usesOldUserIfNewUserInProgress() =
-        testScope.runTest {
-            val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
-            val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
-            userRepository.setUserInfos(listOf(info5, info6))
-            userRepository.setSelectedUserInfo(info5)
-
-            // WHEN the selection of user 6 is only in progress
-            userRepository.setSelectedUserInfo(
-                info6,
-                selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
-            )
-
-            underTest.bitmap
-
-            // THEN we still use user 5 for wallpaper selection
-            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index c282c1e..2b28562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -20,12 +20,15 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.setFlagDefault
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
@@ -41,6 +44,11 @@
 
     private val iconContainer = NotificationIconContainer(context, /* attrs= */ null)
 
+    @Before
+    fun setup() {
+        mSetFlagsRule.setFlagDefault(NotificationIconContainerRefactor.FLAG_NAME)
+    }
+
     @Test
     fun calculateWidthFor_zeroIcons_widthIsZero() {
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f),
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 6b3bd22..15c09b5 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
@@ -70,6 +70,7 @@
 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.scrim.ScrimView;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -141,6 +142,8 @@
     @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+    @Mock private AlternateBouncerToGoneTransitionViewModel
+            mAlternateBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
     @Mock private CoroutineDispatcher mMainDispatcher;
@@ -264,10 +267,12 @@
         when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
         when(mDockManager.isDocked()).thenReturn(false);
 
-        when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
+        when(mKeyguardTransitionInteractor.transition(any(), any()))
                 .thenReturn(emptyFlow());
         when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
                 .thenReturn(emptyFlow());
+        when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
+                .thenReturn(emptyFlow());
 
         mScrimController = new ScrimController(
                 mLightBarController,
@@ -285,6 +290,7 @@
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
+                mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
@@ -992,6 +998,7 @@
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
+                mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
@@ -1775,7 +1782,7 @@
     @Test
     public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        mScrimController.mPrimaryBouncerToGoneTransition.accept(
+        mScrimController.mBouncerToGoneTransition.accept(
                 new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
                         TransitionState.RUNNING, "ScrimControllerTest"));
 
@@ -1787,7 +1794,7 @@
     @Test
     public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
         when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
-        mScrimController.mPrimaryBouncerToGoneTransition.accept(
+        mScrimController.mBouncerToGoneTransition.accept(
                 new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
                         TransitionState.FINISHED, "ScrimControllerTest"));
 
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 b36d09d..46b3996 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
@@ -19,8 +19,6 @@
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -37,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -93,6 +93,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import com.google.common.truth.Truth;
 
@@ -147,6 +148,7 @@
     @Mock private WindowInsetsController mWindowInsetsController;
     @Mock private TaskbarDelegate mTaskbarDelegate;
     @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
+    @Mock private SelectedUserInteractor mSelectedUserInteractor;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -175,7 +177,6 @@
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true);
         mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
-        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, true);
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mFeatureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false);
 
@@ -213,7 +214,8 @@
                         mock(KeyguardTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
-                        () -> mock(KeyguardDismissActionInteractor.class)) {
+                        () -> mock(KeyguardDismissActionInteractor.class),
+                        mSelectedUserInteractor) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -716,7 +718,8 @@
                         mock(KeyguardTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
-                        () -> mock(KeyguardDismissActionInteractor.class)) {
+                        () -> mock(KeyguardDismissActionInteractor.class),
+                        mSelectedUserInteractor) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index beac995..1e31977 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -86,7 +86,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -222,7 +223,8 @@
         HeadsUpManager headsUpManager = mock(HeadsUpManager.class);
         NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
                 new NotificationLaunchAnimatorControllerProvider(
-                        new NotificationExpansionRepository(),
+                        new NotificationLaunchAnimationInteractor(
+                                new NotificationLaunchAnimationRepository()),
                         mock(NotificationListContainer.class),
                         headsUpManager,
                         mJankMonitor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index ee4f208..53c621d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -53,7 +53,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -79,8 +79,8 @@
     private CommandQueue mCommandQueue;
     private FakeMetricsLogger mMetricsLogger;
     private final ShadeController mShadeController = mock(ShadeController.class);
-    private final NotificationsInteractor mNotificationsInteractor =
-            mock(NotificationsInteractor.class);
+    private final NotificationAlertsInteractor mNotificationAlertsInteractor =
+            mock(NotificationAlertsInteractor.class);
     private final KeyguardStateController mKeyguardStateController =
             mock(KeyguardStateController.class);
     private final InitController mInitController = new InitController();
@@ -116,7 +116,7 @@
                 mock(NotificationShadeWindowController.class),
                 mock(DynamicPrivacyController.class),
                 mKeyguardStateController,
-                mNotificationsInteractor,
+                mNotificationAlertsInteractor,
                 mock(LockscreenShadeTransitionController.class),
                 mock(PowerInteractor.class),
                 mCommandQueue,
@@ -226,7 +226,7 @@
                 .setTag("a")
                 .setNotification(n)
                 .build();
-        when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
+        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
 
         assertTrue("When alerts aren't enabled, interruptions are suppressed",
                 mInterruptSuppressor.suppressInterruptions(entry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
new file mode 100644
index 0000000..1e628bd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.content.res.Configuration
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class SystemUIBottomSheetDialogTest : SysuiTestCase() {
+
+    private val configurationController = mock<ConfigurationController>()
+    private val config = mock<Configuration>()
+
+    private lateinit var dialog: SystemUIBottomSheetDialog
+
+    @Before
+    fun setup() {
+        dialog = SystemUIBottomSheetDialog(mContext, configurationController)
+    }
+
+    @Test
+    fun onStart_registersConfigCallback() {
+        dialog.show()
+
+        verify(configurationController).addCallback(any())
+    }
+
+    @Test
+    fun onStop_unregisterConfigCallback() {
+        dialog.show()
+        dialog.dismiss()
+
+        verify(configurationController).removeCallback(any())
+    }
+
+    @Test
+    fun onConfigurationChanged_calledInSubclass() {
+        var onConfigChangedCalled = false
+        val subclass =
+            object : SystemUIBottomSheetDialog(mContext, configurationController) {
+                override fun onConfigurationChanged() {
+                    onConfigChangedCalled = true
+                }
+            }
+
+        subclass.show()
+
+        val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+        verify(configurationController).addCallback(capture(captor))
+        captor.value.onConfigChanged(config)
+
+        assertThat(onConfigChangedCalled).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index e6f8c48..9aafee4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone
 
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import android.os.Handler
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
@@ -80,10 +82,14 @@
     @Mock
     private lateinit var handler: Handler
 
+    private lateinit var featureFlags: FakeFeatureFlags
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
+        featureFlags = FakeFeatureFlags().apply {
+            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
+        }
         controller = UnlockedScreenOffAnimationController(
                 context,
                 wakefulnessLifecycle,
@@ -95,7 +101,8 @@
                 dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
                 interactionJankMonitor,
                 powerManager,
-                handler = handler
+                handler = handler,
+                featureFlags,
         )
         controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index bcb34d6..d1b9b8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -24,13 +24,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Fragment;
@@ -42,27 +37,29 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.widget.FrameLayout;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.common.ui.ConfigurationState;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -72,6 +69,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -276,15 +274,15 @@
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
     }
 
     @Test
@@ -316,7 +314,7 @@
 
         // THEN all views are hidden
         assertEquals(View.INVISIBLE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -332,7 +330,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -349,7 +347,7 @@
 
         // THEN all views are hidden
         assertEquals(View.INVISIBLE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the shade is updated to no longer be open
@@ -360,7 +358,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -374,7 +372,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -388,7 +386,7 @@
 
         // THEN all views are hidden
         assertEquals(View.GONE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -402,7 +400,7 @@
 
         // THEN all views are hidden
         assertEquals(View.GONE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the transition has finished
@@ -411,7 +409,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -444,7 +442,7 @@
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
-        verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
     }
 
     @Test
@@ -500,6 +498,20 @@
     }
 
     @Test
+    public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // Ongoing call started
+        when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, true);
+
+        // Notification area is hidden without delay
+        assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01);
+        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+    }
+
+    @Test
     public void disable_isDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(true);
@@ -682,7 +694,7 @@
                 mLocationPublisher,
                 mMockNotificationAreaController,
                 mShadeExpansionStateManager,
-                mock(FeatureFlags.class),
+                mock(FeatureFlagsClassic.class),
                 mStatusBarIconController,
                 mIconManagerFactory,
                 mCollapsedStatusBarViewModel,
@@ -702,7 +714,12 @@
                 mExecutor,
                 mDumpManager,
                 mStatusBarWindowStateController,
-                mKeyguardUpdateMonitor);
+                mKeyguardUpdateMonitor,
+                mock(NotificationIconContainerStatusBarViewModel.class),
+                mock(ConfigurationState.class),
+                mock(ConfigurationController.class),
+                mock(StatusBarNotificationIconViewStore.class),
+                mock(DemoModeController.class));
     }
 
     private void setUpDaggerComponent() {
@@ -715,18 +732,7 @@
     private void setUpNotificationIconAreaController() {
         mMockNotificationAreaController = mock(NotificationIconAreaController.class);
 
-        mNotificationAreaInner = mock(View.class);
-
-        when(mNotificationAreaInner.getLayoutParams()).thenReturn(
-                new FrameLayout.LayoutParams(100, 100));
-        // We should probably start using a real view so that we don't need to mock these methods.
-        ViewPropertyAnimator viewPropertyAnimator = mock(ViewPropertyAnimator.class);
-        when(mNotificationAreaInner.animate()).thenReturn(viewPropertyAnimator);
-        when(viewPropertyAnimator.alpha(anyFloat())).thenReturn(viewPropertyAnimator);
-        when(viewPropertyAnimator.setDuration(anyLong())).thenReturn(viewPropertyAnimator);
-        when(viewPropertyAnimator.setInterpolator(any())).thenReturn(viewPropertyAnimator);
-        when(viewPropertyAnimator.setStartDelay(anyLong())).thenReturn(viewPropertyAnimator);
-        when(viewPropertyAnimator.withEndAction(any())).thenReturn(viewPropertyAnimator);
+        mNotificationAreaInner = new View(mContext);
 
         when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn(
                 mNotificationAreaInner);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
index d35ce76..8ecf6f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -18,12 +18,11 @@
 
 import android.os.Handler
 import android.os.Looper
-import android.os.UserHandle
 import android.provider.Settings.Global
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -48,15 +47,14 @@
     @Mock private lateinit var logger: TableLogBuffer
     private lateinit var bgHandler: Handler
     private lateinit var scope: CoroutineScope
-    private lateinit var settings: FakeSettings
+    private lateinit var settings: FakeGlobalSettings
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         bgHandler = Handler(Looper.getMainLooper())
         scope = CoroutineScope(IMMEDIATE)
-        settings = FakeSettings()
-        settings.userId = UserHandle.USER_ALL
+        settings = FakeGlobalSettings()
 
         underTest =
             AirplaneModeRepositoryImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 812e91b..610fede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -60,6 +60,8 @@
 
     override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
 
+    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
     fun setDataEnabled(enabled: Boolean) {
         _dataEnabled.value = enabled
     }
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 57f97ec..1f8cc54 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
@@ -123,6 +123,7 @@
                     carrierNetworkChange = testCase.carrierNetworkChange,
                     roaming = testCase.roaming,
                     name = "demo name",
+                    slice = testCase.slice,
                 )
 
             fakeNetworkEventFlow.value = networkModel
@@ -142,6 +143,7 @@
             launch { conn.carrierName.collect {} }
             launch { conn.isEmergencyOnly.collect {} }
             launch { conn.dataConnectionState.collect {} }
+            launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
         }
         return job
     }
@@ -165,6 +167,7 @@
                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
                 assertThat(conn.carrierName.value)
                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+                assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
 
                 // TODO(b/261029387): check these once we start handling them
                 assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -190,6 +193,7 @@
         val carrierNetworkChange: Boolean,
         val roaming: Boolean,
         val name: String,
+        val slice: Boolean,
     ) {
         override fun toString(): String {
             return "INPUT(level=$level, " +
@@ -200,7 +204,8 @@
                 "activity=$activity, " +
                 "carrierNetworkChange=$carrierNetworkChange, " +
                 "roaming=$roaming, " +
-                "name=$name)"
+                "name=$name," +
+                "slice=$slice)"
         }
 
         // Convenience for iterating test data and creating new cases
@@ -214,6 +219,7 @@
             carrierNetworkChange: Boolean? = null,
             roaming: Boolean? = null,
             name: String? = null,
+            slice: Boolean? = null,
         ): TestCase =
             TestCase(
                 level = level ?: this.level,
@@ -225,6 +231,7 @@
                 carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
                 roaming = roaming ?: this.roaming,
                 name = name ?: this.name,
+                slice = slice ?: this.slice,
             )
     }
 
@@ -254,6 +261,7 @@
         // false first so the base case doesn't have roaming set (more common)
         private val roaming = listOf(false, true)
         private val names = listOf("name 1", "name 2")
+        private val slice = listOf(false, true)
 
         @Parameters(name = "{0}") @JvmStatic fun data() = testData()
 
@@ -291,6 +299,7 @@
                     carrierNetworkChange.first(),
                     roaming.first(),
                     names.first(),
+                    slice.first(),
                 )
 
             val tail =
@@ -303,6 +312,7 @@
                         carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
                         roaming.map { baseCase.modifiedBy(roaming = it) },
                         names.map { baseCase.modifiedBy(name = it) },
+                        slice.map { baseCase.modifiedBy(slice = 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 2712b70..d918fa8 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
@@ -549,6 +549,7 @@
             launch { conn.carrierName.collect {} }
             launch { conn.isEmergencyOnly.collect {} }
             launch { conn.dataConnectionState.collect {} }
+            launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
         }
         return job
     }
@@ -574,6 +575,7 @@
                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
                 assertThat(conn.carrierName.value)
                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+                assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
 
                 // TODO(b/261029387) check these once we start handling them
                 assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -599,6 +601,7 @@
         assertThat(conn.isEmergencyOnly.value).isFalse()
         assertThat(conn.isGsm.value).isFalse()
         assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+        assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse()
         job.cancel()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index ede02d1..1c21ebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.net.ConnectivityManager
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
@@ -80,6 +81,7 @@
         )
     private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
     private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
+    private val connectivityManager = mock<ConnectivityManager>()
 
     private val subscriptionModel =
         MutableStateFlow(
@@ -678,6 +680,7 @@
                 subscriptionModel,
                 DEFAULT_NAME_MODEL,
                 SEP,
+                connectivityManager,
                 telephonyManager,
                 systemUiCarrierConfig = mock(),
                 fakeBroadcastDispatcher,
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 8ef82c9..ba64265 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
@@ -19,6 +19,8 @@
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
@@ -85,7 +87,9 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -107,6 +111,7 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
@@ -144,6 +149,7 @@
                 subscriptionModel,
                 DEFAULT_NAME_MODEL,
                 SEP,
+                connectivityManager,
                 telephonyManager,
                 systemUiCarrierConfig,
                 fakeBroadcastDispatcher,
@@ -904,6 +910,45 @@
             assertThat(latest).isFalse()
         }
 
+    @Test
+    fun hasPrioritizedCaps_defaultFalse() {
+        assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse()
+    }
+
+    @Test
+    fun hasPrioritizedCaps_trueWhenAvailable() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+            val callback: NetworkCallback =
+                withArgCaptor<NetworkCallback> {
+                    verify(connectivityManager).registerNetworkCallback(any(), capture())
+                }
+
+            callback.onAvailable(mock())
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+            val callback: NetworkCallback =
+                withArgCaptor<NetworkCallback> {
+                    verify(connectivityManager).registerNetworkCallback(any(), capture())
+                }
+
+            callback.onAvailable(mock())
+
+            assertThat(latest).isTrue()
+
+            callback.onLost(mock())
+
+            assertThat(latest).isFalse()
+        }
+
     private inline fun <reified T> getTelephonyCallbackForType(): T {
         return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 852ed20..889f60a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.net.ConnectivityManager
 import android.telephony.ServiceState
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.CarrierNetworkListener
@@ -96,6 +97,7 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
@@ -129,6 +131,7 @@
                 subscriptionModel,
                 DEFAULT_NAME,
                 SEP,
+                connectivityManager,
                 telephonyManager,
                 systemUiCarrierConfig,
                 fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 9148c75..18ba6c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -180,6 +180,7 @@
             MobileConnectionRepositoryImpl.Factory(
                 context,
                 fakeBroadcastDispatcher,
+                connectivityManager,
                 telephonyManager = telephonyManager,
                 bgDispatcher = dispatcher,
                 logger = logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index de2b6a8..5f4d7bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -48,6 +48,8 @@
             NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
         )
 
+    override val showSliceAttribution = MutableStateFlow(false)
+
     override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
 
     override val carrierName = MutableStateFlow("demo mode")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 218fd33..3936bed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -22,13 +22,15 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
 import android.view.View
+import android.widget.FrameLayout
 import android.widget.ImageView
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
@@ -58,8 +60,8 @@
     private lateinit var testableLooper: TestableLooper
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var viewLogger: MobileViewLogger
     @Mock private lateinit var constants: ConnectivityConstants
@@ -246,7 +248,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x12345678
-        view.onDarkChanged(arrayListOf(), 1.0f, color)
+        val contrast = 0x12344321
+        view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -267,7 +270,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x23456789
-        view.setStaticDrawableColor(color)
+        val contrast = 0x12344321
+        view.setStaticDrawableColor(color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -275,6 +279,35 @@
         ViewUtils.detachView(view)
     }
 
+    @Test
+    fun colorChange_layersUpdateWithContrast() {
+        // Allow the slice, and set it to visible. This cause us to use special color logic
+        flags.set(Flags.NEW_NETWORK_SLICE_UI, true)
+        interactor.showSliceAttribution.value = true
+        createViewModel()
+
+        val view =
+            ModernStatusBarMobileView.constructAndBind(
+                context,
+                viewLogger,
+                SLOT_NAME,
+                viewModel,
+            )
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        val color = 0x23456789
+        val contrast = 0x12344321
+        view.setStaticDrawableColor(color, contrast)
+
+        testableLooper.processAllMessages()
+
+        assertThat(view.getNetTypeContainer().backgroundTintList).isEqualTo(color.colorState())
+        assertThat(view.getNetTypeView().imageTintList).isEqualTo(contrast.colorState())
+
+        ViewUtils.detachView(view)
+    }
+
     private fun View.getGroupView(): View {
         return this.requireViewById(R.id.mobile_group)
     }
@@ -283,10 +316,20 @@
         return this.requireViewById(R.id.mobile_signal)
     }
 
+    private fun View.getNetTypeContainer(): FrameLayout {
+        return this.requireViewById(R.id.mobile_type_container)
+    }
+
+    private fun View.getNetTypeView(): ImageView {
+        return this.requireViewById(R.id.mobile_type)
+    }
+
     private fun View.getDotView(): View {
         return this.requireViewById(R.id.status_bar_dot)
     }
 
+    private fun Int.colorState() = ColorStateList.valueOf(this)
+
     private fun createViewModel() {
         viewModelCommon =
             MobileIconViewModel(
@@ -294,6 +337,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
         viewModel = QsMobileIconViewModel(viewModelCommon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 1878329..1d5487f 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
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 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.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
@@ -47,12 +50,14 @@
 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
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
     private lateinit var commonImpl: MobileIconViewModelCommon
     private lateinit var homeIcon: HomeMobileIconViewModel
@@ -65,6 +70,7 @@
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
 
     private val connectivityRepository = FakeConnectivityRepository()
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var constants: ConnectivityConstants
@@ -133,6 +139,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
 
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 796d5ec..c831e62 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
@@ -26,7 +27,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,12 +60,14 @@
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class MobileIconViewModelTest : SysuiTestCase() {
     private var connectivityRepository = FakeConnectivityRepository()
 
@@ -74,6 +82,7 @@
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -595,6 +604,46 @@
             containerJob.cancel()
         }
 
+    @Test
+    fun netTypeBackground_flagOff_isNull() =
+        testScope.runTest {
+            flags.set(NEW_NETWORK_SLICE_UI, false)
+            createAndSetViewModel()
+
+            val latest by collectLastValue(underTest.networkTypeBackground)
+
+            repository.hasPrioritizedNetworkCapabilities.value = true
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun netTypeBackground_flagOn_nullWhenNoPrioritizedCapabilities() =
+        testScope.runTest {
+            flags.set(NEW_NETWORK_SLICE_UI, true)
+            createAndSetViewModel()
+
+            val latest by collectLastValue(underTest.networkTypeBackground)
+
+            repository.hasPrioritizedNetworkCapabilities.value = false
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun netTypeBackground_flagOn_notNullWhenPrioritizedCapabilities() =
+        testScope.runTest {
+            flags.set(NEW_NETWORK_SLICE_UI, true)
+            createAndSetViewModel()
+
+            val latest by collectLastValue(underTest.networkTypeBackground)
+
+            repository.hasPrioritizedNetworkCapabilities.value = true
+
+            assertThat(latest)
+                .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
+        }
+
     private fun createAndSetViewModel() {
         underTest =
             MobileIconViewModel(
@@ -602,6 +651,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index eb6f2f8..f3e334e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -41,15 +44,18 @@
 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
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class MobileIconsViewModelTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsViewModel
     private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@@ -77,6 +83,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index e44ff8e..28d632d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -41,6 +41,7 @@
      * setting mobile connected && validated, since the default state is disconnected && not
      * validated
      */
+    @JvmOverloads
     fun setMobileConnected(
         default: Boolean = true,
         validated: Boolean = true,
@@ -53,6 +54,7 @@
     }
 
     /** Similar convenience method for ethernet */
+    @JvmOverloads
     fun setEthernetConnected(
         default: Boolean = true,
         validated: Boolean = true,
@@ -64,6 +66,7 @@
             )
     }
 
+    @JvmOverloads
     fun setWifiConnected(
         default: Boolean = true,
         validated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index b4039d9..028a58c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -77,9 +77,10 @@
     fun onDarkChanged_bindingReceivesIconAndDecorTint() {
         val view = createAndInitView()
 
-        view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678)
+        view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
 
         assertThat(binding.iconTint).isEqualTo(0x12345678)
+        assertThat(binding.contrastTint).isEqualTo(0x12344321)
         assertThat(binding.decorTint).isEqualTo(0x12345678)
     }
 
@@ -87,9 +88,10 @@
     fun setStaticDrawableColor_bindingReceivesIconTint() {
         val view = createAndInitView()
 
-        view.setStaticDrawableColor(0x12345678)
+        view.setStaticDrawableColor(0x12345678, 0x12344321)
 
         assertThat(binding.iconTint).isEqualTo(0x12345678)
+        assertThat(binding.contrastTint).isEqualTo(0x12344321)
     }
 
     @Test
@@ -144,13 +146,15 @@
 
     inner class TestBinding : ModernStatusBarViewBinding {
         var iconTint: Int? = null
+        var contrastTint: Int? = null
         var decorTint: Int? = null
         var onVisibilityStateChangedCalled: Boolean = false
 
         var shouldIconBeVisibleInternal: Boolean = true
 
-        override fun onIconTintChanged(newTint: Int) {
+        override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
             iconTint = newTint
+            this.contrastTint = contrastTint
         }
 
         override fun onDecorTintChanged(newTint: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index c2f5665..3126362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -201,11 +201,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiDefault)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isTrue()
@@ -229,11 +229,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiDefault)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isDefaultNetwork).thenReturn(false)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isFalse()
@@ -526,13 +526,14 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
                     whenever(this.subscriptionId).thenReturn(567)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -546,11 +547,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             whenever(wifiManager.maxSignalLevel).thenReturn(5)
 
             getCallback().onWifiEntriesChanged()
@@ -566,12 +568,13 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
 
             getCallback().onWifiEntriesChanged()
 
@@ -628,11 +631,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(false)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
@@ -717,12 +721,14 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -730,6 +736,7 @@
 
             // WHEN we lose our current network
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
             // THEN we update to no network
@@ -767,6 +774,56 @@
         }
 
     @Test
+    fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                }
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+        }
+
+    @Test
+    fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(false)
+                }
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+        }
+
+    @Test
     fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
         testScope.runTest {
             featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index a27f899..d75a452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,9 +25,9 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -229,7 +229,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x12345678
-        view.onDarkChanged(arrayListOf(), 1.0f, color)
+        val contrast = 0x12344321
+        view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -244,7 +245,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x23456789
-        view.setStaticDrawableColor(color)
+        val contrast = 0x12344321
+        view.setStaticDrawableColor(color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index e761635..a1da167 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -75,6 +75,8 @@
     private BluetoothAdapter mMockAdapter;
     private List<CachedBluetoothDevice> mDevices;
 
+    private FakeExecutor mBackgroundExecutor;
+
     @Before
     public void setup() throws Exception {
         mTestableLooper = TestableLooper.get(this);
@@ -91,6 +93,7 @@
         when(mMockBluetoothManager.getProfileManager())
                 .thenReturn(mock(LocalBluetoothProfileManager.class));
         mMockDumpManager = mock(DumpManager.class);
+        mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
 
         BluetoothRepository bluetoothRepository =
                 new FakeBluetoothRepository(mMockBluetoothManager);
@@ -101,6 +104,7 @@
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
                 bluetoothRepository,
+                mBackgroundExecutor,
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager,
                 mMockAdapter);
@@ -205,6 +209,7 @@
         mBluetoothControllerImpl.onAclConnectionStateChanged(device,
                 BluetoothProfile.STATE_CONNECTED);
         mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET);
+        mBackgroundExecutor.runAllReady();
 
         assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive());
         assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
@@ -290,6 +295,7 @@
                 BluetoothProfile.LE_AUDIO, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -300,6 +306,7 @@
                 BluetoothProfile.HEADSET, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -310,6 +317,7 @@
                 BluetoothProfile.A2DP, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -320,6 +328,7 @@
                 BluetoothProfile.HEARING_AID, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -337,6 +346,8 @@
         mBluetoothControllerImpl.onDeviceAdded(device2);
         mBluetoothControllerImpl.onDeviceAdded(device3);
 
+        mBackgroundExecutor.runAllReady();
+
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
 
@@ -349,6 +360,7 @@
 
         mBluetoothControllerImpl.onDeviceAdded(device1);
         mBluetoothControllerImpl.onDeviceAdded(device2);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isFalse();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
index 6094135..361fa5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.wrapper.BuildInfo
@@ -67,19 +68,22 @@
 
     private lateinit var mainExecutor: FakeExecutor
     private lateinit var testableLooper: TestableLooper
-    private lateinit var settings: FakeSettings
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var globalSettings: FakeGlobalSettings
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         mainExecutor = FakeExecutor(FakeSystemClock())
-        settings = FakeSettings()
+        secureSettings = FakeSettings()
+        globalSettings = FakeGlobalSettings()
         `when`(userTracker.userId).thenReturn(START_USER)
         whenever(buildInfo.isDebuggable).thenReturn(false)
         controller = DeviceProvisionedControllerImpl(
-                settings,
-                settings,
+                secureSettings,
+                globalSettings,
                 userTracker,
                 dumpManager,
                 buildInfo,
@@ -108,7 +112,7 @@
 
     @Test
     fun testProvisionedWhenCreated() {
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
         init()
 
         assertThat(controller.isDeviceProvisioned).isTrue()
@@ -116,7 +120,7 @@
 
     @Test
     fun testFrpActiveWhenCreated() {
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
         init()
 
         assertThat(controller.isFrpActive).isTrue()
@@ -124,7 +128,7 @@
 
     @Test
     fun testUserSetupWhenCreated() {
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
         init()
 
         assertThat(controller.isUserSetup(START_USER))
@@ -134,7 +138,7 @@
     fun testDeviceProvisionedChange() {
         init()
 
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isDeviceProvisioned).isTrue()
@@ -144,7 +148,7 @@
     fun testFrpActiveChange() {
         init()
 
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isFrpActive).isTrue()
@@ -154,7 +158,7 @@
     fun testUserSetupChange() {
         init()
 
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isUserSetup(START_USER)).isTrue()
@@ -165,7 +169,7 @@
         init()
         val otherUser = 10
 
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isUserSetup(START_USER)).isFalse()
@@ -175,7 +179,7 @@
     @Test
     fun testCurrentUserSetup() {
         val otherUser = 10
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
         init()
 
         assertThat(controller.isCurrentUserSetup).isFalse()
@@ -219,7 +223,7 @@
         init()
         controller.addCallback(listener)
 
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
 
@@ -234,7 +238,7 @@
         init()
         controller.addCallback(listener)
 
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
 
@@ -249,7 +253,7 @@
         init()
         controller.addCallback(listener)
 
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
 
@@ -266,9 +270,9 @@
         controller.removeCallback(listener)
 
         switchUser(10)
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
 
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index 1250228..d33806e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import com.android.systemui.flags.FakeFeatureFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -77,11 +78,13 @@
     private lateinit var view: FrameLayout
     private lateinit var testableLooper: TestableLooper
     private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        featureFlags = FakeFeatureFlags()
 
         view = LayoutInflater.from(context)
                 .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -98,6 +101,7 @@
                 dozeParameters,
                 screenOffAnimationController,
                 userSwitchDialogController,
+                featureFlags,
                 uiEventLogger)
 
         ViewUtils.attachView(view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index cae892f..5c960b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -67,6 +68,8 @@
     @Mock
     private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
     private KeyguardUpdateMonitorLogger mLogger;
     @Mock
     private FeatureFlags mFeatureFlags;
@@ -84,7 +87,8 @@
                 mKeyguardUnlockAnimationControllerLazy,
                 mLogger,
                 mDumpManager,
-                mFeatureFlags);
+                mFeatureFlags,
+                mSelectedUserInteractor);
     }
 
     @Test
@@ -93,20 +97,20 @@
     }
 
     @Test
-    public void testFaceAuthEnabledChanged_calledWhenFaceEnrollmentStateChanges() {
+    public void testFaceAuthEnrolleddChanged_calledWhenFaceEnrollmentStateChanges() {
         KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
 
-        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(false);
         verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
         mKeyguardStateController.addCallback(callback);
-        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isFalse();
+        assertThat(mKeyguardStateController.isFaceEnrolled()).isFalse();
 
-        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(true);
         mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged(
                 BiometricSourceType.FACE);
 
-        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isTrue();
-        verify(callback).onFaceAuthEnabledChanged();
+        assertThat(mKeyguardStateController.isFaceEnrolled()).isTrue();
+        verify(callback).onFaceEnrolledChanged();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index b78e839..63de068 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -23,26 +23,27 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import java.util.Date
 
 @RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class VariableDateViewControllerTest : SysuiTestCase() {
 
@@ -57,12 +58,15 @@
     private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock
     private lateinit var view: VariableDateView
+    @Mock
+    private lateinit var shadeInteractor: ShadeInteractor
     @Captor
     private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
 
+    private val qsExpansion = MutableStateFlow(0F)
+
     private var lastText: String? = null
 
-    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     private lateinit var systemClock: FakeSystemClock
     private lateinit var testableLooper: TestableLooper
     private lateinit var testableHandler: Handler
@@ -80,7 +84,7 @@
         systemClock = FakeSystemClock()
         systemClock.setCurrentTimeMillis(TIME_STAMP)
 
-        shadeExpansionStateManager = ShadeExpansionStateManager()
+        `when`(shadeInteractor.qsExpansion).thenReturn(qsExpansion)
 
         `when`(view.longerPattern).thenReturn(LONG_PATTERN)
         `when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
@@ -91,6 +95,7 @@
             Unit
         }
         `when`(view.isAttachedToWindow).thenReturn(true)
+        `when`(view.viewTreeObserver).thenReturn(mock())
 
         val date = Date(TIME_STAMP)
         longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
@@ -103,12 +108,12 @@
         }
 
         controller = VariableDateViewController(
-                systemClock,
-                broadcastDispatcher,
-                shadeExpansionStateManager,
-                mock(),
-                testableHandler,
-                view
+            systemClock,
+            broadcastDispatcher,
+            shadeInteractor,
+            mock(),
+            testableHandler,
+            view
         )
 
         controller.init()
@@ -180,7 +185,7 @@
 
     @Test
     fun testQsExpansionTrue_ignoreAtMostMeasureRequests() {
-        shadeExpansionStateManager.onQsExpansionFractionChanged(0f)
+        qsExpansion.value = 0f
 
         onMeasureListenerCaptor.value.onMeasureAction(
                 getTextLength(shortText).toInt(),
@@ -195,7 +200,7 @@
 
     @Test
     fun testQsExpansionFalse_acceptAtMostMeasureRequests() {
-        shadeExpansionStateManager.onQsExpansionFractionChanged(1f)
+        qsExpansion.value = 1f
 
         onMeasureListenerCaptor.value.onMeasureAction(
                 getTextLength(shortText).toInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 66c5aaa..6825f65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
-import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -67,7 +67,7 @@
     UserTracker mUserTracker;
     private ZenModeControllerImpl mController;
 
-    private final FakeSettings mGlobalSettings = new FakeSettings();
+    private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
 
     @Before
     public void setUp() {
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 1bc346d..96db09e 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
@@ -57,7 +57,6 @@
             PowerInteractorFactory.create().powerInteractor,
             FakeFeatureFlagsClassic(),
             sceneTestUtils.sceneContainerFlags,
-            sceneTestUtils.deviceEntryRepository,
             FakeKeyguardBouncerRepository(),
             FakeConfigurationRepository(),
             FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
new file mode 100644
index 0000000..4eb1591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.system.DeviceStateRepositoryImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DeviceStateRepositoryTest : SysuiTestCase() {
+
+    private val foldProvider = mock<FoldProvider>()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private val foldStateRepository = DeviceStateRepositoryImpl(foldProvider) { r -> r.run() }
+
+    @Test
+    fun onHingeAngleUpdate_received() =
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.isFolded)
+            val foldCallback = argumentCaptor<FoldProvider.FoldCallback>()
+
+            verify(foldProvider).registerCallback(capture(foldCallback), any())
+
+            foldCallback.value.onFoldUpdated(true)
+            assertThat(flowValue()).isEqualTo(true)
+
+            foldCallback.value.onFoldUpdated(false)
+            assertThat(flowValue()).isEqualTo(false)
+        }
+
+    @Test
+    fun onHingeAngleUpdate_unregisters() {
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.isFolded)
+
+            verify(foldProvider).registerCallback(any(), any())
+        }
+        verify(foldProvider).unregisterCallback(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
new file mode 100644
index 0000000..0651323
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FoldStateRepositoryTest : SysuiTestCase() {
+
+    private val foldStateProvider = mock<FoldStateProvider>()
+    private val foldUpdatesListener = argumentCaptor<FoldStateProvider.FoldUpdatesListener>()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private val foldStateRepository = FoldStateRepositoryImpl(foldStateProvider)
+    @Test
+    fun onHingeAngleUpdate_received() =
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.hingeAngle)
+
+            verify(foldStateProvider).addCallback(capture(foldUpdatesListener))
+            foldUpdatesListener.value.onHingeAngleUpdate(42f)
+
+            assertThat(flowValue()).isEqualTo(42f)
+        }
+
+    @Test
+    fun onFoldUpdate_received() =
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.foldUpdate)
+
+            verify(foldStateProvider).addCallback(capture(foldUpdatesListener))
+            foldUpdatesListener.value.onFoldUpdate(FOLD_UPDATE_START_OPENING)
+
+            assertThat(flowValue()).isEqualTo(FoldUpdate.START_OPENING)
+        }
+
+    @Test
+    fun foldUpdates_mappedCorrectly() {
+        mapOf(
+                FOLD_UPDATE_START_OPENING to FoldUpdate.START_OPENING,
+                FOLD_UPDATE_START_CLOSING to FoldUpdate.START_CLOSING,
+                FOLD_UPDATE_FINISH_HALF_OPEN to FoldUpdate.FINISH_HALF_OPEN,
+                FOLD_UPDATE_FINISH_FULL_OPEN to FoldUpdate.FINISH_FULL_OPEN,
+                FOLD_UPDATE_FINISH_CLOSED to FoldUpdate.FINISH_CLOSED
+            )
+            .forEach { (id, expected) ->
+                assertThat(FoldUpdate.fromFoldUpdateId(id)).isEqualTo(expected)
+            }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index e249cec..0d78ae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -56,14 +56,14 @@
 
     private lateinit var underTest: UserRepositoryImpl
 
-    private lateinit var globalSettings: FakeSettings
+    private lateinit var globalSettings: FakeGlobalSettings
     private lateinit var tracker: FakeUserTracker
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        globalSettings = FakeSettings()
+        globalSettings = FakeGlobalSettings()
         tracker = FakeUserTracker()
     }
 
@@ -282,20 +282,17 @@
             com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
             true,
         )
-        globalSettings.putIntForUser(
+        globalSettings.putInt(
             UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
             if (isSimpleUserSwitcher) 1 else 0,
-            UserHandle.USER_SYSTEM,
         )
-        globalSettings.putIntForUser(
+        globalSettings.putInt(
             Settings.Global.ADD_USERS_WHEN_LOCKED,
             if (isAddUsersFromLockscreen) 1 else 0,
-            UserHandle.USER_SYSTEM,
         )
-        globalSettings.putIntForUser(
+        globalSettings.putInt(
             Settings.Global.USER_SWITCHER_ENABLED,
             if (isUserSwitcherEnabled) 1 else 0,
-            UserHandle.USER_SYSTEM,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
new file mode 100644
index 0000000..60fe7d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.user.domain.interactor
+
+import android.content.pm.UserInfo
+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.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SelectedUserInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: SelectedUserInteractor
+
+    private val userRepository = FakeUserRepository()
+
+    @Before
+    fun setUp() {
+        userRepository.setUserInfos(USER_INFOS)
+        underTest =
+            SelectedUserInteractor(
+                userRepository,
+                FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
+            )
+    }
+
+    @Test
+    fun getSelectedUserIdReturnsId() {
+        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+        val actualId = underTest.getSelectedUserId()
+
+        assertThat(actualId).isEqualTo(USER_INFOS[0].id)
+    }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(100, "First user", 0),
+                UserInfo(101, "Second user", 0),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index c56266d..1968d75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -88,7 +88,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
-class UserInteractorTest : SysuiTestCase() {
+class UserSwitcherInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var manager: UserManager
@@ -102,7 +102,7 @@
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    private lateinit var underTest: UserInteractor
+    private lateinit var underTest: UserSwitcherInteractor
 
     private lateinit var spyContext: Context
     private lateinit var testScope: TestScope
@@ -665,8 +665,8 @@
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val callback1: UserInteractor.UserCallback = mock()
-            val callback2: UserInteractor.UserCallback = mock()
+            val callback1: UserSwitcherInteractor.UserCallback = mock()
+            val callback2: UserSwitcherInteractor.UserCallback = mock()
             underTest.addCallback(callback1)
             underTest.addCallback(callback2)
             runCurrent()
@@ -1117,7 +1117,7 @@
             userRepository.setSelectedUserInfo(userInfo)
         }
         underTest =
-            UserInteractor(
+            UserSwitcherInteractor(
                 applicationContext = spyContext,
                 repository = userRepository,
                 activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index a8db368..7041eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -243,9 +243,8 @@
             userRepository.setSelectedUserInfo(USER_0)
         }
         return StatusBarUserChipViewModel(
-            context = context,
             interactor =
-                UserInteractor(
+                UserSwitcherInteractor(
                     applicationContext = context,
                     repository = userRepository,
                     activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c236b12..686f492 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.util.mockito.any
@@ -157,8 +157,8 @@
 
         underTest =
             UserSwitcherViewModel(
-                userInteractor =
-                    UserInteractor(
+                userSwitcherInteractor =
+                    UserSwitcherInteractor(
                         applicationContext = context,
                         repository = userRepository,
                         activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
index aaf8d07..94100fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.util.ui
 
 import android.testing.AndroidTestingRunner
@@ -20,9 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -35,64 +36,193 @@
     @Test
     fun animatableEvent_updatesValue() = runTest {
         val events = MutableSharedFlow<AnimatableEvent<Int>>()
-        val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+        val values = events.toAnimatedValueFlow()
         val value by collectLastValue(values)
         runCurrent()
 
         events.emit(AnimatableEvent(value = 1, startAnimating = false))
 
-        assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+        assertThat(value?.value).isEqualTo(1)
+        assertThat(value?.isAnimating).isFalse()
     }
 
     @Test
     fun animatableEvent_startAnimation() = runTest {
         val events = MutableSharedFlow<AnimatableEvent<Int>>()
-        val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+        val values = events.toAnimatedValueFlow()
         val value by collectLastValue(values)
         runCurrent()
 
         events.emit(AnimatableEvent(value = 1, startAnimating = true))
 
-        assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = true))
+        assertThat(value?.value).isEqualTo(1)
+        assertThat(value?.isAnimating).isTrue()
     }
 
     @Test
     fun animatableEvent_startAnimation_alreadyAnimating() = runTest {
         val events = MutableSharedFlow<AnimatableEvent<Int>>()
-        val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+        val values = events.toAnimatedValueFlow()
         val value by collectLastValue(values)
         runCurrent()
 
         events.emit(AnimatableEvent(value = 1, startAnimating = true))
         events.emit(AnimatableEvent(value = 2, startAnimating = true))
 
-        assertThat(value).isEqualTo(AnimatedValue(value = 2, isAnimating = true))
+        assertThat(value?.value).isEqualTo(2)
+        assertThat(value?.isAnimating).isTrue()
     }
 
     @Test
     fun animatedValue_stopAnimating() = runTest {
         val events = MutableSharedFlow<AnimatableEvent<Int>>()
-        val stopEvent = MutableSharedFlow<Unit>()
-        val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+        val values = events.toAnimatedValueFlow()
         val value by collectLastValue(values)
         runCurrent()
 
         events.emit(AnimatableEvent(value = 1, startAnimating = true))
-        stopEvent.emit(Unit)
+        assertThat(value?.isAnimating).isTrue()
+        value?.stopAnimating()
 
-        assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+        assertThat(value?.value).isEqualTo(1)
+        assertThat(value?.isAnimating).isFalse()
     }
 
     @Test
-    fun animatedValue_stopAnimating_notAnimating() = runTest {
+    fun animatedValue_stopAnimatingPrevValue_doesNothing() = runTest {
         val events = MutableSharedFlow<AnimatableEvent<Int>>()
-        val stopEvent = MutableSharedFlow<Unit>()
-        val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
-        values.launchIn(backgroundScope)
+        val values = events.toAnimatedValueFlow()
+        val value by collectLastValue(values)
         runCurrent()
 
-        events.emit(AnimatableEvent(value = 1, startAnimating = false))
+        events.emit(AnimatableEvent(value = 1, startAnimating = true))
+        val prevValue = value
+        assertThat(prevValue?.isAnimating).isTrue()
 
-        assertThat(stopEvent.subscriptionCount.value).isEqualTo(0)
+        events.emit(AnimatableEvent(value = 2, startAnimating = true))
+        assertThat(value?.isAnimating).isTrue()
+        prevValue?.stopAnimating()
+
+        assertThat(value?.value).isEqualTo(2)
+        assertThat(value?.isAnimating).isTrue()
+    }
+
+    @Test
+    fun zipValues_applyTransform() {
+        val animating = AnimatedValue.Animating(1) {}
+        val notAnimating = AnimatedValue.NotAnimating(2)
+        val sum = zip(animating, notAnimating) { a, b -> a + b }
+        assertThat(sum.value).isEqualTo(3)
+    }
+
+    @Test
+    fun zipValues_firstIsAnimating_resultIsAnimating() {
+        var stopped = false
+        val animating = AnimatedValue.Animating(1) { stopped = true }
+        val notAnimating = AnimatedValue.NotAnimating(2)
+        val sum = zip(animating, notAnimating) { a, b -> a + b }
+        assertThat(sum.isAnimating).isTrue()
+
+        sum.stopAnimating()
+        assertThat(stopped).isTrue()
+    }
+
+    @Test
+    fun zipValues_secondIsAnimating_resultIsAnimating() {
+        var stopped = false
+        val animating = AnimatedValue.Animating(1) { stopped = true }
+        val notAnimating = AnimatedValue.NotAnimating(2)
+        val sum = zip(notAnimating, animating) { a, b -> a + b }
+        assertThat(sum.isAnimating).isTrue()
+
+        sum.stopAnimating()
+        assertThat(stopped).isTrue()
+    }
+
+    @Test
+    fun zipValues_bothAnimating_resultIsAnimating() {
+        var firstStopped = false
+        var secondStopped = false
+        val first = AnimatedValue.Animating(1) { firstStopped = true }
+        val second = AnimatedValue.Animating(2) { secondStopped = true }
+        val sum = zip(first, second) { a, b -> a + b }
+        assertThat(sum.isAnimating).isTrue()
+
+        sum.stopAnimating()
+        assertThat(firstStopped).isTrue()
+        assertThat(secondStopped).isTrue()
+    }
+
+    @Test
+    fun zipValues_neitherAnimating_resultIsNotAnimating() {
+        val first = AnimatedValue.NotAnimating(1)
+        val second = AnimatedValue.NotAnimating(2)
+        val sum = zip(first, second) { a, b -> a + b }
+        assertThat(sum.isAnimating).isFalse()
+    }
+
+    @Test
+    fun mapAnimatedValue_isAnimating() {
+        var stopped = false
+        val animating = AnimatedValue.Animating(3) { stopped = true }
+        val squared = animating.map { it * it }
+        assertThat(squared.value).isEqualTo(9)
+        assertThat(squared.isAnimating).isTrue()
+        squared.stopAnimating()
+        assertThat(stopped).isTrue()
+    }
+
+    @Test
+    fun mapAnimatedValue_notAnimating() {
+        val notAnimating = AnimatedValue.NotAnimating(3)
+        val squared = notAnimating.map { it * it }
+        assertThat(squared.value).isEqualTo(9)
+        assertThat(squared.isAnimating).isFalse()
+    }
+
+    @Test
+    fun flattenAnimatingValue_neitherAnimating() {
+        val nested = AnimatedValue.NotAnimating(AnimatedValue.NotAnimating(10))
+        val flattened = nested.flatten()
+        assertThat(flattened.value).isEqualTo(10)
+        assertThat(flattened.isAnimating).isFalse()
+    }
+
+    @Test
+    fun flattenAnimatingValue_outerAnimating() {
+        var stopped = false
+        val inner = AnimatedValue.NotAnimating(10)
+        val nested = AnimatedValue.Animating(inner) { stopped = true }
+        val flattened = nested.flatten()
+        assertThat(flattened.value).isEqualTo(10)
+        assertThat(flattened.isAnimating).isTrue()
+        flattened.stopAnimating()
+        assertThat(stopped).isTrue()
+    }
+
+    @Test
+    fun flattenAnimatingValue_innerAnimating() {
+        var stopped = false
+        val inner = AnimatedValue.Animating(10) { stopped = true }
+        val nested = AnimatedValue.NotAnimating(inner)
+        val flattened = nested.flatten()
+        assertThat(flattened.value).isEqualTo(10)
+        assertThat(flattened.isAnimating).isTrue()
+        flattened.stopAnimating()
+        assertThat(stopped).isTrue()
+    }
+
+    @Test
+    fun flattenAnimatingValue_bothAnimating() {
+        var innerStopped = false
+        var outerStopped = false
+        val inner = AnimatedValue.Animating(10) { innerStopped = true }
+        val nested = AnimatedValue.Animating(inner) { outerStopped = true }
+        val flattened = nested.flatten()
+        assertThat(flattened.value).isEqualTo(10)
+        assertThat(flattened.isAnimating).isTrue()
+        flattened.stopAnimating()
+        assertThat(innerStopped).isTrue()
+        assertThat(outerStopped).isTrue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
index a853f1d..c69f5c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
@@ -209,6 +209,11 @@
                         new int[]{MetricsEvent.POWER_OVERHEAT_ALARM,
                                 MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM},
                         Events.VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED},
+                {Events.EVENT_ODI_CAPTIONS_CLICK, null, "writeEvent odi_captions_click", null,
+                        Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED},
+                {Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK, null,
+                        "writeEvent odi_captions_tooltip_click", null,
+                        Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED}
         });
     }
 }
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 28fc5db..c4c7472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -42,6 +42,7 @@
 import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -52,11 +53,12 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.test.core.view.MotionEventBuilder;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
@@ -65,11 +67,18 @@
 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.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.statusbar.policy.FakeConfigurationController;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import dagger.Lazy;
+
+import junit.framework.Assert;
 
 import org.junit.After;
 import org.junit.Before;
@@ -122,6 +131,8 @@
     @Mock CsdWarningDialog mCsdWarningDialog;
     @Mock
     DevicePostureController mPostureController;
+    @Mock
+    private Lazy<SecureSettings> mLazySecureSettings;
 
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
             new CsdWarningDialog.Factory() {
@@ -133,6 +144,7 @@
 
     private FakeFeatureFlags mFeatureFlags;
     private int mLongestHideShowAnimationDuration = 250;
+    private FakeSettings mSecureSettings;
 
     @Rule
     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@@ -162,6 +174,10 @@
 
         mFeatureFlags = new FakeFeatureFlags();
 
+        mSecureSettings = new FakeSettings();
+
+        when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -177,7 +193,8 @@
                 mPostureController,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFeatureFlags);
+                mFeatureFlags,
+                mLazySecureSettings);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -242,6 +259,17 @@
     }
 
     @Test
+    public void testSetTimeoutValue_ComputeTimeout() {
+        mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000);
+        Mockito.reset(mAccessibilityMgr);
+        mDialog.init(0, null);
+        mDialog.rescheduleTimeoutH();
+        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+                7000,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
+    @Test
     public void testComputeTimeout_tooltip() {
         Mockito.reset(mAccessibilityMgr);
         mDialog.showCaptionsTooltip();
@@ -675,6 +703,60 @@
         }
     }
 
+    /**
+     * The click should be a single tap, thus we inject a down and an up event.
+     */
+    @Test
+    public void clickCaptionsButton_logsUiEvent() {
+        UiEventLoggerFake logger = new UiEventLoggerFake();
+        Events.sUiEventLogger = logger;
+        MotionEvent down = MotionEventBuilder.newBuilder()
+                .setAction(MotionEvent.ACTION_DOWN).build();
+        MotionEvent up = MotionEventBuilder.newBuilder()
+                .setAction(MotionEvent.ACTION_UP).build();
+
+        mODICaptionsIcon.onTouchEvent(down);
+        mODICaptionsIcon.onTouchEvent(up);
+        mTestableLooper.moveTimeForward(300); // to confirm it was only a single tap
+        mTestableLooper.processAllMessages();
+
+        boolean foundCaptionLog = false;
+        for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) {
+            if (event.eventId
+                    == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED.getId()) {
+                foundCaptionLog = true;
+                break;
+            }
+        }
+        Assert.assertTrue("Did not log the captions button click.", foundCaptionLog);
+    }
+
+    /**
+     * Pressing the small x button at top right dismisses the captions tooltip.
+     */
+    @Test
+    public void dismissCaptionsTooltip_logsUiEvent() {
+        UiEventLoggerFake logger = new UiEventLoggerFake();
+        Events.sUiEventLogger = logger;
+        mDialog.showCaptionsTooltip();
+        assumeNotNull(mDialog.mODICaptionsTooltipView);
+        View dismissButton = mDialog.mODICaptionsTooltipView.findViewById(R.id.dismiss);
+
+        dismissButton.performClick();
+
+        boolean foundCaptionLog = false;
+        for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) {
+            if (event.eventId
+                    == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED.getId()
+            ) {
+                foundCaptionLog = true;
+                break;
+            }
+        }
+        Assert.assertTrue("Did not log the captions tooltip dismiss button click.",
+                foundCaptionLog);
+    }
+
     @After
     public void teardown() {
         // Detailed logs to track down timeout issues in b/299491332
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 468c5a7..fc2030f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,15 +16,20 @@
 
 package com.android.systemui.wallpapers;
 
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -111,7 +116,8 @@
         when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
 
         // set up wallpaper manager
-        when(mWallpaperManager.getBitmapAsUser(eq(ActivityManager.getCurrentUser()), anyBoolean()))
+        when(mWallpaperManager.getBitmapAsUser(
+                eq(ActivityManager.getCurrentUser()), anyBoolean(), anyInt(), anyBoolean()))
                 .thenReturn(mWallpaperBitmap);
         when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
 
@@ -208,6 +214,7 @@
         ImageWallpaper.CanvasEngine spyEngine = spy(engine);
         doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
         doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+        doReturn(FLAG_SYSTEM | FLAG_LOCK).when(spyEngine).getWallpaperFlags();
         doAnswer(invocation -> {
             ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
             return null;
@@ -216,7 +223,7 @@
     }
 
     private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
-        when(mWallpaperManager.peekBitmapDimensions())
+        when(mWallpaperManager.peekBitmapDimensions(anyInt(), anyBoolean()))
                 .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
         when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
         when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
@@ -234,9 +241,7 @@
         clearInvocations(mSurfaceHolder);
         setBitmapDimensions(bitmapWidth, bitmapHeight);
 
-        ImageWallpaper imageWallpaper = createImageWallpaper();
-        ImageWallpaper.CanvasEngine engine =
-                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        ImageWallpaper.CanvasEngine engine = getSpyEngine();
         engine.onCreate(mSurfaceHolder);
 
         verify(mSurfaceHolder, times(1)).setFixedSize(
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 c832702..ec808c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -94,7 +94,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -156,7 +155,8 @@
 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.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -324,6 +324,8 @@
     @Mock
     private ShadeWindowLogger mShadeWindowLogger;
     @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
     private NotifPipelineFlags mNotifPipelineFlags;
     @Mock
     private Icon mAppBubbleIcon;
@@ -400,7 +402,6 @@
                 powerInteractor,
                 featureFlags,
                 sceneContainerFlags,
-                new FakeDeviceEntryRepository(),
                 new FakeKeyguardBouncerRepository(),
                 configurationRepository,
                 shadeRepository,
@@ -433,6 +434,7 @@
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
+                mSelectedUserInteractor,
                 powerInteractor);
 
         ResourcesSplitShadeStateController splitShadeStateController =
@@ -450,7 +452,7 @@
                         keyguardTransitionInteractor,
                         powerInteractor,
                         new FakeUserSetupRepository(),
-                        mock(UserInteractor.class),
+                        mock(UserSwitcherInteractor.class),
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
@@ -476,7 +478,8 @@
                 mAuthController,
                 mShadeExpansionStateManager,
                 () -> mShadeInteractor,
-                mShadeWindowLogger
+                mShadeWindowLogger,
+                () -> mSelectedUserInteractor
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index 41dbc14..b820ca6 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -68,13 +68,26 @@
 
     private final Object mLock = new Object();
     private final TestHandler mTestHandler = new TestHandler();
+    private final long mStartTime;
+    private long mTotalTimeDelta = 0;
+
     /**
-     * initializing the start time with {@link SystemClock#uptimeMillis()} reduces the discrepancies
-     * with various internals of classes like ValueAnimator which can sometimes read that clock via
+     * Construct an AnimatorTestRule with a custom start time.
+     * @see #AnimatorTestRule()
+     */
+    public AnimatorTestRule(long startTime) {
+        mStartTime = startTime;
+    }
+
+    /**
+     * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}.
+     * Initializing the start time with this clock reduces the discrepancies with various internals
+     * of classes like ValueAnimator which can sometimes read that clock via
      * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}.
      */
-    private final long mStartTime = SystemClock.uptimeMillis();
-    private long mTotalTimeDelta = 0;
+    public AnimatorTestRule() {
+        this(SystemClock.uptimeMillis());
+    }
 
     @NonNull
     @Override
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt
new file mode 100644
index 0000000..cdd0ff7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt
@@ -0,0 +1,68 @@
+package android.hardware.display
+
+import android.content.Context
+
+class FakeAmbientDisplayConfiguration(context: Context) : AmbientDisplayConfiguration(context) {
+    var fakePulseOnNotificationEnabled = true
+
+    override fun pulseOnNotificationEnabled(user: Int) = fakePulseOnNotificationEnabled
+
+    override fun pulseOnNotificationAvailable() = TODO("Not yet implemented")
+
+    override fun pickupGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun dozePickupSensorAvailable() = TODO("Not yet implemented")
+
+    override fun tapGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun tapSensorAvailable() = TODO("Not yet implemented")
+
+    override fun doubleTapGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun doubleTapSensorAvailable() = TODO("Not yet implemented")
+
+    override fun quickPickupSensorEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun screenOffUdfpsEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun wakeScreenGestureAvailable() = TODO("Not yet implemented")
+
+    override fun wakeLockScreenGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun wakeDisplayGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun getWakeLockScreenDebounce() = TODO("Not yet implemented")
+
+    override fun doubleTapSensorType() = TODO("Not yet implemented")
+
+    override fun tapSensorTypeMapping() = TODO("Not yet implemented")
+
+    override fun longPressSensorType() = TODO("Not yet implemented")
+
+    override fun udfpsLongPressSensorType() = TODO("Not yet implemented")
+
+    override fun quickPickupSensorType() = TODO("Not yet implemented")
+
+    override fun pulseOnLongPressEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun alwaysOnEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun alwaysOnAvailable() = TODO("Not yet implemented")
+
+    override fun alwaysOnAvailableForUser(user: Int) = TODO("Not yet implemented")
+
+    override fun ambientDisplayComponent() = TODO("Not yet implemented")
+
+    override fun accessibilityInversionEnabled(user: Int) = TODO("Not yet implemented")
+
+    override fun ambientDisplayAvailable() = TODO("Not yet implemented")
+
+    override fun dozeSuppressed(user: Int) = TODO("Not yet implemented")
+
+    override fun disableDozeSettings(userId: Int) = TODO("Not yet implemented")
+
+    override fun disableDozeSettings(shouldDisableNonUserConfigurable: Boolean, userId: Int) =
+        TODO("Not yet implemented")
+
+    override fun restoreDozeSettings(userId: Int) = TODO("Not yet implemented")
+}
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 a882f89..e6e6b7b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,9 +15,8 @@
  */
 package com.android.systemui;
 
-import static com.android.systemui.animation.FakeDialogLaunchAnimatorKt.fakeDialogLaunchAnimator;
+import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -27,6 +26,7 @@
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.testing.LeakCheck;
 import android.testing.TestWithLooperRule;
@@ -37,14 +37,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
-import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -55,7 +48,6 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
 /**
@@ -73,6 +65,9 @@
             new AndroidXAnimatorIsolationRule();
 
     @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule
     public SysuiTestableContext mContext = new SysuiTestableContext(
             InstrumentationRegistry.getContext(), getLeakCheck());
     @Rule
@@ -85,7 +80,7 @@
 
     public TestableDependency mDependency;
     private Instrumentation mRealInstrumentation;
-    private FakeBroadcastDispatcher mFakeBroadcastDispatcher;
+    private SysuiTestDependency mSysuiDependency;
 
     @Before
     public void SysuiSetup() throws Exception {
@@ -93,20 +88,11 @@
         if (isRobolectricTest()) {
             mContext = mContext.createDefaultDisplayContext();
         }
-        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
-        initializer.init(true);
-        mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
-        Dependency.setInstance(mDependency);
-        mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
-                mContext,
-                mContext.getMainExecutor(),
-                mock(Looper.class),
-                mock(Executor.class),
-                mock(DumpManager.class),
-                mock(BroadcastDispatcherLogger.class),
-                mock(UserTracker.class),
-                shouldFailOnLeakedReceiver());
-
+        // Set the value of a single gantry flag inside the com.android.systemui package to
+        // ensure all flags in that package are faked (and thus require a value to be set).
+        mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG);
+        mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
+        mDependency = mSysuiDependency.install();
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
         when(inst.getContext()).thenAnswer(invocation -> {
@@ -118,18 +104,6 @@
                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
         });
         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
-        // 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
-        // reference and are never sent to the Context. This will also prevent a real
-        // BroadcastDispatcher from actually registering receivers.
-        mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-
-        // Make sure that all tests on any SystemUIDialog does not crash because this dependency
-        // is missing (constructing the actual one would throw).
-        // TODO(b/219008720): Remove this.
-        mDependency.injectMockDependency(SystemUIDialogManager.class);
-        mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator());
     }
 
     protected boolean shouldFailOnLeakedReceiver() {
@@ -149,8 +123,9 @@
         }
         disallowTestableLooperAsMainThread();
         mContext.cleanUpReceivers(this.getClass().getSimpleName());
-        if (mFakeBroadcastDispatcher != null) {
-            mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName());
+        FakeBroadcastDispatcher dispatcher = getFakeBroadcastDispatcher();
+        if (dispatcher != null) {
+            dispatcher.cleanUpReceivers(this.getClass().getSimpleName());
         }
     }
 
@@ -177,7 +152,7 @@
     }
 
     public FakeBroadcastDispatcher getFakeBroadcastDispatcher() {
-        return mFakeBroadcastDispatcher;
+        return mSysuiDependency.getFakeBroadcastDispatcher();
     }
 
     public SysuiTestableContext getContext() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
new file mode 100644
index 0000000..d89d7b0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
@@ -0,0 +1,60 @@
+package com.android.systemui
+
+import android.annotation.SuppressLint
+import android.os.Looper
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.fakeDialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import java.util.concurrent.Executor
+import org.mockito.Mockito.mock
+
+class SysuiTestDependency(
+    val context: SysuiTestableContext,
+    private val shouldFailOnLeakedReceiver: Boolean
+) {
+    var fakeBroadcastDispatcher: FakeBroadcastDispatcher? = null
+
+    @SuppressLint("VisibleForTests")
+    fun install(): TestableDependency {
+        val initializer: SystemUIInitializer = SystemUIInitializerImpl(context)
+        initializer.init(true)
+
+        val dependency = TestableDependency(initializer.sysUIComponent.createDependency())
+        Dependency.setInstance(dependency)
+
+        dependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
+
+        // Make sure that all tests on any SystemUIDialog does not crash because this dependency
+        // is missing (constructing the actual one would throw).
+        // TODO(b/219008720): Remove this.
+        dependency.injectMockDependency(SystemUIDialogManager::class.java)
+        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
+        // reference and are never sent to the Context. This will also prevent a real
+        // BroadcastDispatcher from actually registering receivers.
+        fakeBroadcastDispatcher =
+            FakeBroadcastDispatcher(
+                context,
+                context.mainExecutor,
+                mock(Looper::class.java),
+                mock(Executor::class.java),
+                mock(DumpManager::class.java),
+                mock(BroadcastDispatcherLogger::class.java),
+                mock(UserTracker::class.java),
+                shouldFailOnLeakedReceiver
+            )
+        dependency.injectTestDependency(BroadcastDispatcher::class.java, fakeBroadcastDispatcher)
+        return dependency
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java
new file mode 100644
index 0000000..fdd26eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+/**
+ * UiOffloadThread that can be used for testing as part of {@link TestableDependency}.
+ */
+public class TestUiOffloadThread extends UiOffloadThread {
+    private final Handler mTestHandler;
+
+    public TestUiOffloadThread(Looper looper) {
+        mTestHandler = new Handler(looper);
+    }
+
+    @Override
+    public Future<?> execute(Runnable runnable) {
+        Looper myLooper = Looper.myLooper();
+        if (myLooper != null && myLooper.isCurrentThread()) {
+            try {
+                runnable.run();
+                return CompletableFuture.completedFuture(null);
+            } catch (Exception e) {
+                return CompletableFuture.failedFuture(e);
+            }
+        }
+
+        final CompletableFuture<?> future = new CompletableFuture<>();
+        mTestHandler.post(() -> {
+            try {
+                runnable.run();
+                future.complete(null);
+            } catch (Exception e) {
+                future.completeExceptionally(e);
+            }
+        });
+
+        return future;
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/FakeAccessibilityDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/FakeAccessibilityDataLayerModule.kt
new file mode 100644
index 0000000..baf1006
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/FakeAccessibilityDataLayerModule.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.accessibility.data
+
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeAccessibilityRepositoryModule::class])
+object FakeAccessibilityDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
index 8444c7b..4085b1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -16,8 +16,20 @@
 
 package com.android.systemui.accessibility.data.repository
 
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 
+@SysUISingleton
 class FakeAccessibilityRepository(
-    override val isTouchExplorationEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
-) : AccessibilityRepository
+    override val isTouchExplorationEnabled: MutableStateFlow<Boolean>,
+) : AccessibilityRepository {
+    @Inject constructor() : this(MutableStateFlow(false))
+}
+
+@Module
+interface FakeAccessibilityRepositoryModule {
+    @Binds fun bindFake(fake: FakeAccessibilityRepository): AccessibilityRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
index 0ced19e..ba9c5ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
@@ -28,11 +28,21 @@
  * advanced together.
  */
 class AnimatorTestRule : TestRule {
+    // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(),
+    // then copy that time to the platform rule so that the two clocks are in sync.
     private val androidxRule = androidx.core.animation.AnimatorTestRule()
-    private val platformRule = android.animation.AnimatorTestRule()
+    private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime)
     private val advanceAndroidXTimeBy =
         Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) }
 
+    /** Access the mStartTime field; bypassing the restriction of being on a looper thread. */
+    private val androidx.core.animation.AnimatorTestRule.startTime: Long
+        get() =
+            javaClass.getDeclaredField("mStartTime").let { field ->
+                field.isAccessible = true
+                field.getLong(this)
+            }
+
     /**
      * Chain is for simplicity not to force a particular order; order should not matter, because
      * each rule affects a different AnimationHandler classes, and no callbacks to code under test
@@ -55,4 +65,11 @@
         //  animation from one to start later than the other.
         platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy)
     }
+
+    /**
+     * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is
+     * a different time than the time tracked by {@link SystemClock}.
+     */
+    val currentTime: Long
+        get() = androidxRule.currentTime
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 10b284a..8353cf7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -34,7 +34,10 @@
     private val _scaleForResolution = MutableStateFlow(1f)
     override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
 
-    suspend fun onAnyConfigurationChange() {
+    private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
+    private val colors = mutableMapOf<Int, MutableStateFlow<Int>>()
+
+    fun onAnyConfigurationChange() {
         _onAnyConfigurationChange.tryEmit(Unit)
     }
 
@@ -42,12 +45,12 @@
         _scaleForResolution.value = scale
     }
 
-    override fun getResolutionScale(): Float {
-        return _scaleForResolution.value
-    }
+    override fun getResolutionScale(): Float = _scaleForResolution.value
 
-    override fun getDimensionPixelSize(id: Int): Int {
-        throw IllegalStateException("Don't use for tests")
+    override fun getDimensionPixelSize(id: Int): Int = pixelSizes[id]?.value ?: 0
+
+    fun setDimensionPixelSize(id: Int, pixelSize: Int) {
+        pixelSizes.getOrPut(id) { MutableStateFlow(pixelSize) }.value = pixelSize
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt
new file mode 100644
index 0000000..902e852
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt
@@ -0,0 +1,19 @@
+package com.android.systemui.communal.data.repository
+
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+import android.provider.Settings.Secure.HubModeTutorialState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [CommunalTutorialRepository] */
+class FakeCommunalTutorialRepository() : CommunalTutorialRepository {
+    private val _tutorialSettingState = MutableStateFlow(HUB_MODE_TUTORIAL_NOT_STARTED)
+    override val tutorialSettingState: StateFlow<Int> = _tutorialSettingState
+    override suspend fun setTutorialState(@HubModeTutorialState state: Int) {
+        setTutorialSettingState(state)
+    }
+
+    fun setTutorialSettingState(@HubModeTutorialState state: Int) {
+        _tutorialSettingState.value = state
+    }
+}
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 1a8c583..08adda3 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,6 +1,8 @@
 package com.android.systemui.communal.data.repository
 
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -8,8 +10,16 @@
 class FakeCommunalWidgetRepository : CommunalWidgetRepository {
     private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null)
     override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
+    override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
+
+    private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
 
     fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
         _stopwatchAppWidgetInfo.value = appWidgetInfo
     }
+
+    fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
+        _communalWidgets.value = inventory
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
index cffbf02..36f0882 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.data
 
+import com.android.systemui.accessibility.data.FakeAccessibilityDataLayerModule
 import com.android.systemui.authentication.data.FakeAuthenticationDataLayerModule
 import com.android.systemui.bouncer.data.repository.FakeBouncerDataLayerModule
 import com.android.systemui.common.ui.data.FakeCommonDataLayerModule
@@ -30,6 +31,7 @@
 @Module(
     includes =
         [
+            FakeAccessibilityDataLayerModule::class,
             FakeAuthenticationDataLayerModule::class,
             FakeBouncerDataLayerModule::class,
             FakeCommonDataLayerModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 5cd09d8..5fd0b4f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -43,9 +43,10 @@
     mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
 
 /** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository() : DisplayRepository {
-    private val flow = MutableSharedFlow<Set<Display>>()
-    private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
+class FakeDisplayRepository : DisplayRepository {
+    private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
+    private val pendingDisplayFlow =
+        MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
 
     /** Emits [value] as [displays] flow value. */
     suspend fun emit(value: Set<Display>) = flow.emit(value)
@@ -59,7 +60,7 @@
     override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
         get() = pendingDisplayFlow
 
-    private val _displayChangeEvent = MutableSharedFlow<Int>()
+    private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
     override val displayChangeEvent: Flow<Int> = _displayChangeEvent
     suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
new file mode 100644
index 0000000..95b5316
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
@@ -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 com.android.systemui.keyevent.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyEventRepository() : KeyEventRepository {
+    private val _isPowerButtonDown = MutableStateFlow(false)
+    override val isPowerButtonDown: Flow<Boolean> = _isPowerButtonDown.asStateFlow()
+
+    fun setPowerButtonDown(isDown: Boolean) {
+        _isPowerButtonDown.value = isDown
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt
index abf72af..6710072 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.keyguard.data
 
 import com.android.systemui.keyguard.data.repository.FakeCommandQueueModule
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepositoryModule
 import dagger.Module
@@ -24,6 +25,7 @@
     includes =
         [
             FakeCommandQueueModule::class,
+            FakeDeviceEntryFaceAuthRepositoryModule::class,
             FakeKeyguardRepositoryModule::class,
             FakeKeyguardTransitionRepositoryModule::class,
         ]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index e91e955..85261123 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -34,7 +34,7 @@
         get() = _isFingerprintAuthCurrentlyAllowed
 
     private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
-    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+    override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
         get() = _isFaceAuthEnrolledAndEnabled
 
     private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 322fb28..e289083 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,15 +17,20 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import dagger.Binds
+import dagger.Module
+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.filterNotNull
 
-class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+@SysUISingleton
+class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
 
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
@@ -66,3 +71,8 @@
         _runningAuthRequest.value = null
     }
 }
+
+@Module
+interface FakeDeviceEntryFaceAuthRepositoryModule {
+    @Binds fun bindFake(fake: FakeDeviceEntryFaceAuthRepository): DeviceEntryFaceAuthRepository
+}
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 9de7ad8..88a88c7 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
@@ -29,12 +29,12 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 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.asStateFlow
-import javax.inject.Inject
 
 /** Fake implementation of [KeyguardRepository] */
 @SysUISingleton
@@ -58,6 +58,9 @@
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
+    private val _isKeyguardUnlocked = MutableStateFlow(false)
+    override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
+
     private val _isKeyguardOccluded = MutableStateFlow(false)
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
 
@@ -182,8 +185,8 @@
         _lastDozeTapToWakePosition.value = position
     }
 
-    fun setAodAvailable(isAodAvailable: Boolean) {
-        _isAodAvailable.value = isAodAvailable
+    override fun setAodAvailable(value: Boolean) {
+        _isAodAvailable.value = value
     }
 
     fun setDreaming(isDreaming: Boolean) {
@@ -202,8 +205,8 @@
         _dozeAmount.value = dozeAmount
     }
 
-    fun setBiometricUnlockState(state: BiometricUnlockModel) {
-        _biometricUnlockState.tryEmit(state)
+    override fun setBiometricUnlockState(value: BiometricUnlockModel) {
+        _biometricUnlockState.tryEmit(value)
     }
 
     fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
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 e160548..71e2bc1 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
@@ -42,7 +42,7 @@
         _transitions.emit(step)
     }
 
-    override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
+    override fun startTransition(info: TransitionInfo): UUID? {
         return if (info.animator == null) UUID.randomUUID() else null
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 8e96b52..fc34903 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -16,11 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.app.ActivityManager
 import android.content.Context
 import android.os.Handler
-import android.os.UserManager
-import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -28,7 +25,6 @@
 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.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -36,21 +32,13 @@
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
-import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.utils.UserRestrictionChecker
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.TestScope
 import org.mockito.Mockito.mock
 
@@ -64,8 +52,6 @@
     fun create(
         context: Context,
         testScope: TestScope,
-        broadcastDispatcher: FakeBroadcastDispatcher,
-        dispatcher: CoroutineDispatcher,
         trustRepository: FakeTrustRepository = FakeTrustRepository(),
         keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -74,6 +60,7 @@
             FakeFeatureFlagsClassic().apply {
                 set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true)
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+                set(Flags.REFACTOR_GETCURRENTUSER, true)
             },
         powerRepository: FakePowerRepository = FakePowerRepository(),
         userRepository: FakeUserRepository = FakeUserRepository(),
@@ -92,6 +79,7 @@
                 keyguardUpdateMonitor,
                 trustRepository,
                 testScope.backgroundScope,
+                mock(SelectedUserInteractor::class.java),
             )
         val alternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -103,38 +91,11 @@
                 keyguardUpdateMonitor,
             )
         val powerInteractorWithDeps =
-                PowerInteractorFactory.create(
-                        repository = powerRepository,
-                )
-        val userInteractor =
-            UserInteractor(
-                applicationContext = context,
-                repository = userRepository,
-                mock(ActivityStarter::class.java),
-                keyguardInteractor =
-                    KeyguardInteractorFactory.create(
-                            repository = keyguardRepository,
-                            bouncerRepository = bouncerRepository,
-                            featureFlags = featureFlags,
-                        )
-                        .keyguardInteractor,
-                featureFlags = featureFlags,
-                manager = mock(UserManager::class.java),
-                headlessSystemUserMode = mock(HeadlessSystemUserMode::class.java),
-                applicationScope = testScope.backgroundScope,
-                telephonyInteractor =
-                    TelephonyInteractor(
-                        repository = FakeTelephonyRepository(),
-                    ),
-                broadcastDispatcher = broadcastDispatcher,
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = dispatcher,
-                activityManager = mock(ActivityManager::class.java),
-                refreshUsersScheduler = mock(RefreshUsersScheduler::class.java),
-                guestUserInteractor = mock(GuestUserInteractor::class.java),
-                uiEventLogger = mock(UiEventLogger::class.java),
-                userRestrictionChecker = mock(UserRestrictionChecker::class.java),
+            PowerInteractorFactory.create(
+                repository = powerRepository,
             )
+        val selectedUserInteractor =
+            SelectedUserInteractor(repository = userRepository, flags = featureFlags)
         return WithDependencies(
             trustRepository = trustRepository,
             keyguardRepository = keyguardRepository,
@@ -149,7 +110,7 @@
                     primaryBouncerInteractor,
                     alternateBouncerInteractor,
                     powerInteractorWithDeps.powerInteractor,
-                    userInteractor,
+                    selectedUserInteractor,
                 ),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 82ce802..d2ff9bc5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,7 +19,6 @@
 
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -45,7 +44,6 @@
         sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
         repository: FakeKeyguardRepository = FakeKeyguardRepository(),
         commandQueue: FakeCommandQueue = FakeCommandQueue(),
-        deviceEntryRepository: FakeDeviceEntryRepository = FakeDeviceEntryRepository(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
         configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
         shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -57,7 +55,6 @@
             commandQueue = commandQueue,
             featureFlags = featureFlags,
             sceneContainerFlags = sceneContainerFlags,
-            deviceEntryRepository = deviceEntryRepository,
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
             shadeRepository = shadeRepository,
@@ -67,7 +64,6 @@
                 commandQueue = commandQueue,
                 featureFlags = featureFlags,
                 sceneContainerFlags = sceneContainerFlags,
-                deviceEntryRepository = deviceEntryRepository,
                 bouncerRepository = bouncerRepository,
                 configurationRepository = configurationRepository,
                 shadeRepository = shadeRepository,
@@ -87,7 +83,6 @@
         val commandQueue: FakeCommandQueue,
         val featureFlags: FakeFeatureFlags,
         val sceneContainerFlags: SceneContainerFlags,
-        val deviceEntryRepository: FakeDeviceEntryRepository,
         val bouncerRepository: FakeKeyguardBouncerRepository,
         val configurationRepository: FakeConfigurationRepository,
         val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index bf77b1a..cddb007 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -41,19 +41,19 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.security.data.repository.SecurityRepositoryImpl
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.FakeSecurityController
 import com.android.systemui.statusbar.policy.FakeUserInfoController
 import com.android.systemui.statusbar.policy.SecurityController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.data.repository.UserSwitcherRepository
 import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.settings.GlobalSettings
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -69,8 +69,8 @@
     private val scheduler: TestCoroutineScheduler,
 ) {
     /** Enable or disable the user switcher in the settings. */
-    fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
-        settings.putBoolForUser(Settings.Global.USER_SWITCHER_ENABLED, enabled, userId)
+    fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean) {
+        settings.putBool(Settings.Global.USER_SWITCHER_ENABLED, enabled)
 
         // The settings listener is processing messages on the bgHandler (usually backed by a
         // testableLooper in tests), so let's make sure we process the callback before continuing.
@@ -102,7 +102,7 @@
         deviceProvisionedController: DeviceProvisionedController = mock(),
         qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
         fgsManagerController: FgsManagerController = mock(),
-        userInteractor: UserInteractor = mock(),
+        userSwitcherInteractor: UserSwitcherInteractor = mock(),
         securityRepository: SecurityRepository = securityRepository(),
         foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
         userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
@@ -116,7 +116,7 @@
             deviceProvisionedController,
             qsSecurityFooterUtils,
             fgsManagerController,
-            userInteractor,
+            userSwitcherInteractor,
             securityRepository,
             foregroundServicesRepository,
             userSwitcherRepository,
@@ -149,20 +149,20 @@
         bgHandler: Handler = Handler(testableLooper.looper),
         bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
         userManager: UserManager = mock(),
-        userTracker: UserTracker = FakeUserTracker(),
+        userRepository: UserRepository = FakeUserRepository(),
         userSwitcherController: UserSwitcherController = mock(),
         userInfoController: UserInfoController = FakeUserInfoController(),
-        settings: GlobalSettings = FakeSettings(),
+        settings: GlobalSettings = FakeGlobalSettings(),
     ): UserSwitcherRepository {
         return UserSwitcherRepositoryImpl(
             context,
             bgHandler,
             bgDispatcher,
             userManager,
-            userTracker,
             userSwitcherController,
             userInfoController,
             settings,
+            userRepository,
         )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
index f62bf60..1efa74b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.base.interactor
 
+import android.os.UserHandle
+
 class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
 
     var handleResult: Boolean = false
@@ -23,7 +25,7 @@
         DisabledByPolicyInteractor.PolicyResult.TileEnabled
 
     override suspend fun isDisabled(
-        userId: Int,
+        user: UserHandle,
         userRestriction: String?
     ): DisabledByPolicyInteractor.PolicyResult = policyResult
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 1cb4ab7..2b3330f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -16,65 +16,39 @@
 
 package com.android.systemui.qs.tiles.base.interactor
 
+import android.os.UserHandle
 import javax.annotation.CheckReturnValue
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
 
 class FakeQSTileDataInteractor<T>(
-    private val dataFlow: MutableSharedFlow<FakeData<T>> =
-        MutableSharedFlow(replay = Int.MAX_VALUE),
+    private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE),
     private val availabilityFlow: MutableSharedFlow<Boolean> =
         MutableSharedFlow(replay = Int.MAX_VALUE),
 ) : QSTileDataInteractor<T> {
 
-    private val mutableDataRequests = mutableListOf<QSTileDataRequest>()
-    val dataRequests: List<QSTileDataRequest> = mutableDataRequests
+    private val mutableDataRequests = mutableListOf<DataRequest>()
+    val dataRequests: List<DataRequest> = mutableDataRequests
 
-    private val mutableAvailabilityRequests = mutableListOf<Unit>()
-    val availabilityRequests: List<Unit> = mutableAvailabilityRequests
+    private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>()
+    val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests
 
-    @CheckReturnValue
-    fun emitData(data: T): FilterEmit =
-        object : FilterEmit {
-            override fun forRequest(request: QSTileDataRequest): Boolean =
-                dataFlow.tryEmit(FakeData(data, DataFilter.ForRequest(request)))
-            override fun forAnyRequest(): Boolean = dataFlow.tryEmit(FakeData(data, DataFilter.Any))
-        }
+    @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data)
 
     fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
     suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
 
-    override fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<T> {
-        mutableDataRequests.add(qsTileDataRequest)
-        return dataFlow
-            .filter {
-                when (it.filter) {
-                    is DataFilter.Any -> true
-                    is DataFilter.ForRequest -> it.filter.request == qsTileDataRequest
-                }
-            }
-            .map { it.data }
+    override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> {
+        mutableDataRequests.add(DataRequest(user))
+        return triggers.flatMapLatest { dataFlow }
     }
 
-    override fun availability(): Flow<Boolean> {
-        mutableAvailabilityRequests.add(Unit)
+    override fun availability(user: UserHandle): Flow<Boolean> {
+        mutableAvailabilityRequests.add(AvailabilityRequest(user))
         return availabilityFlow
     }
 
-    interface FilterEmit {
-        fun forRequest(request: QSTileDataRequest): Boolean
-        fun forAnyRequest(): Boolean
-    }
-
-    class FakeData<T>(
-        val data: T,
-        val filter: DataFilter,
-    )
-
-    sealed class DataFilter {
-        object Any : DataFilter()
-        class ForRequest(val request: QSTileDataRequest) : DataFilter()
-    }
+    data class DataRequest(val user: UserHandle)
+    data class AvailabilityRequest(val user: UserHandle)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 9c99cb5..597d52d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -16,22 +16,19 @@
 
 package com.android.systemui.qs.tiles.base.interactor
 
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 
 class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> {
 
     private val mutex: Mutex = Mutex()
-    private val mutableInputs: MutableList<FakeInput<T>> = mutableListOf()
+    private val mutableInputs: MutableList<QSTileInput<T>> = mutableListOf()
 
-    val inputs: List<FakeInput<T>> = mutableInputs
+    val inputs: List<QSTileInput<T>> = mutableInputs
 
-    fun lastInput(): FakeInput<T>? = inputs.lastOrNull()
+    fun lastInput(): QSTileInput<T>? = inputs.lastOrNull()
 
-    override suspend fun handleInput(userAction: QSTileUserAction, currentData: T) {
-        mutex.withLock { mutableInputs.add(FakeInput(userAction, currentData)) }
+    override suspend fun handleInput(input: QSTileInput<T>) {
+        mutex.withLock { mutableInputs.add(input) }
     }
-
-    data class FakeInput<T>(val userAction: QSTileUserAction, val data: T)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
new file mode 100644
index 0000000..de72a7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.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.tiles.viewmodel
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+class FakeQSTileConfigProvider : QSTileConfigProvider {
+
+    private val configs: MutableMap<String, QSTileConfig> = mutableMapOf()
+
+    override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec)
+
+    fun putConfig(tileSpec: TileSpec, config: QSTileConfig) {
+        configs[tileSpec.spec] = config
+    }
+
+    fun removeConfig(tileSpec: TileSpec): QSTileConfig? = configs.remove(tileSpec.spec)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
index 201926d..2a0ee88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
@@ -16,10 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
-import androidx.annotation.StringRes
 import com.android.internal.logging.InstanceId
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 object QSTileConfigTestBuilder {
@@ -29,8 +26,7 @@
 
     class BuildingScope {
         var tileSpec: TileSpec = TileSpec.create("test_spec")
-        var tileIcon: Icon = Icon.Resource(0, ContentDescription.Resource(0))
-        @StringRes var tileLabel: Int = 0
+        var uiConfig: QSTileUIConfig = QSTileUIConfig.Empty
         var instanceId: InstanceId = InstanceId.fakeInstanceId(0)
         var metricsSpec: String = tileSpec.spec
         var policy: QSTilePolicy = QSTilePolicy.NoRestrictions
@@ -38,8 +34,7 @@
         fun build() =
             QSTileConfig(
                 tileSpec,
-                tileIcon,
-                tileLabel,
+                uiConfig,
                 instanceId,
                 metricsSpec,
                 policy,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index a4881bc..17384351 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -185,7 +185,6 @@
             commandQueue = FakeCommandQueue(),
             featureFlags = featureFlags,
             sceneContainerFlags = sceneContainerFlags,
-            deviceEntryRepository = FakeDeviceEntryRepository(),
             bouncerRepository = FakeKeyguardBouncerRepository(),
             configurationRepository = FakeConfigurationRepository(),
             shadeRepository = FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 4307ff9..7494ccf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -69,10 +69,16 @@
         _userId = _userInfo.id
         _userHandle = UserHandle.of(_userId)
 
+        onBeforeUserSwitching()
         onUserChanging()
         onUserChanged()
     }
 
+    fun onBeforeUserSwitching(userId: Int = _userId) {
+        val copy = callbacks.toList()
+        copy.forEach { it.onBeforeUserSwitching(userId) }
+    }
+
     fun onUserChanging(userId: Int = _userId) {
         val copy = callbacks.toList()
         copy.forEach { it.onUserChanging(userId, userContext) {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
new file mode 100644
index 0000000..19fdb6d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
@@ -0,0 +1,159 @@
+package com.android.systemui.statusbar
+
+import android.view.View
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+
+class FakeStatusBarStateController : SysuiStatusBarStateController {
+    @JvmField var state = StatusBarState.SHADE
+
+    @JvmField var upcomingState = StatusBarState.SHADE
+
+    @JvmField var lastState = StatusBarState.SHADE
+
+    @JvmField var dozing = false
+
+    @JvmField var expanded = false
+
+    @JvmField var pulsing = false
+
+    @JvmField var dreaming = false
+
+    @JvmField var dozeAmount = 0.0f
+
+    @JvmField var interpolatedDozeAmount = 0.0f
+
+    @JvmField var dozeAmountTarget = 0.0f
+
+    @JvmField var leaveOpen = false
+
+    @JvmField var keyguardRequested = false
+
+    var lastSetDozeAmountView: View? = null
+        private set
+
+    var lastSetDozeAmountAnimated = false
+        private set
+
+    var lastSystemBarAppearance = 0
+        private set
+
+    var lastSystemBarBehavior = 0
+        private set
+
+    var lastSystemBarRequestedVisibleTypes = 0
+        private set
+
+    var lastSystemBarPackageName: String? = null
+        private set
+
+    private val _callbacks = mutableSetOf<StatusBarStateController.StateListener>()
+
+    @JvmField val callbacks: Set<StatusBarStateController.StateListener> = _callbacks
+
+    private var fullscreen = false
+
+    override fun start() {}
+
+    override fun getState() = state
+
+    override fun setState(newState: Int, force: Boolean): Boolean {
+        val oldState = this.state
+        newState != oldState || force || return false
+
+        callbacks.forEach { it.onStatePreChange(oldState, newState) }
+        this.lastState = oldState
+        this.state = newState
+        setUpcomingState(newState)
+        callbacks.forEach { it.onStateChanged(newState) }
+        callbacks.forEach { it.onStatePostChange() }
+        return true
+    }
+
+    override fun getCurrentOrUpcomingState() = upcomingState
+
+    override fun setUpcomingState(upcomingState: Int) {
+        upcomingState != this.upcomingState || return
+        this.upcomingState = upcomingState
+        callbacks.forEach { it.onUpcomingStateChanged(upcomingState) }
+    }
+
+    override fun isDozing() = dozing
+
+    override fun setIsDozing(dozing: Boolean): Boolean {
+        dozing != this.dozing || return false
+        this.dozing = dozing
+        callbacks.forEach { it.onDozingChanged(dozing) }
+        return true
+    }
+
+    override fun isExpanded() = expanded
+
+    fun fakeShadeExpansionFullyChanged(expanded: Boolean) {
+        expanded != this.expanded || return
+        this.expanded = expanded
+        callbacks.forEach { it.onExpandedChanged(expanded) }
+    }
+
+    override fun isPulsing() = pulsing
+
+    override fun setPulsing(pulsing: Boolean) {
+        pulsing != this.pulsing || return
+        this.pulsing = pulsing
+        callbacks.forEach { it.onPulsingChanged(pulsing) }
+    }
+
+    override fun isDreaming() = dreaming
+
+    override fun setIsDreaming(drreaming: Boolean): Boolean {
+        dreaming != this.dreaming || return false
+        this.dreaming = dreaming
+        callbacks.forEach { it.onDreamingChanged(dreaming) }
+        return true
+    }
+
+    override fun getDozeAmount() = dozeAmount
+
+    override fun setAndInstrumentDozeAmount(view: View?, dozeAmount: Float, animated: Boolean) {
+        dozeAmountTarget = dozeAmount
+        lastSetDozeAmountView = view
+        lastSetDozeAmountAnimated = animated
+        if (!animated) {
+            this.dozeAmount = dozeAmount
+        }
+    }
+
+    override fun leaveOpenOnKeyguardHide() = leaveOpen
+
+    override fun setLeaveOpenOnKeyguardHide(leaveOpen: Boolean) {
+        this.leaveOpen = leaveOpen
+    }
+
+    override fun getInterpolatedDozeAmount() = interpolatedDozeAmount
+
+    fun fakeInterpolatedDozeAmountChanged(interpolatedDozeAmount: Float) {
+        this.interpolatedDozeAmount = interpolatedDozeAmount
+        callbacks.forEach { it.onDozeAmountChanged(dozeAmount, interpolatedDozeAmount) }
+    }
+
+    override fun goingToFullShade() = state == StatusBarState.SHADE && leaveOpen
+
+    override fun fromShadeLocked() = lastState == StatusBarState.SHADE_LOCKED
+
+    override fun isKeyguardRequested(): Boolean = keyguardRequested
+
+    override fun setKeyguardRequested(keyguardRequested: Boolean) {
+        this.keyguardRequested = keyguardRequested
+    }
+
+    override fun addCallback(listener: StatusBarStateController.StateListener?) {
+        _callbacks.add(listener!!)
+    }
+
+    override fun addCallback(listener: StatusBarStateController.StateListener?, rank: Int) {
+        throw RuntimeException("addCallback with rank unsupported")
+    }
+
+    override fun removeCallback(listener: StatusBarStateController.StateListener?) {
+        _callbacks.remove(listener!!)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
index e59f642..8f18e13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
@@ -15,8 +15,10 @@
  */
 package com.android.systemui.statusbar.data
 
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositoryModule
 import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule
 import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule
+import com.android.systemui.statusbar.phone.data.FakeStatusBarPhoneDataLayerModule
 import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule
 import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule
 import dagger.Module
@@ -25,7 +27,9 @@
     includes =
         [
             FakeStatusBarDisableFlagsDataLayerModule::class,
+            FakeStatusBarModeRepositoryModule::class,
             FakeStatusBarNotificationsDataLayerModule::class,
+            FakeStatusBarPhoneDataLayerModule::class,
             FakeStatusBarPipelineDataLayerModule::class,
             FakeStatusBarPolicyDataLayerModule::class,
         ]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 61ba464..f25d282 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -18,17 +18,17 @@
 
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
 import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeStatusBarModeRepository : StatusBarModeRepository {
+class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository {
     override val isTransientShown = MutableStateFlow(false)
     override val isInFullscreenMode = MutableStateFlow(false)
     override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
     override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
 
-    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {}
-
     override fun showTransient() {
         isTransientShown.value = true
     }
@@ -36,3 +36,8 @@
         isTransientShown.value = false
     }
 }
+
+@Module
+interface FakeStatusBarModeRepositoryModule {
+    @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
new file mode 100644
index 0000000..d2c3b7a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.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.phone.data
+
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeDarkIconRepositoryModule::class]) object FakeStatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
new file mode 100644
index 0000000..50d3f0a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeDarkIconRepository @Inject constructor() : DarkIconRepository {
+    override val darkState = MutableStateFlow(DarkChange(emptyList(), 0f, 0))
+}
+
+@Module
+interface FakeDarkIconRepositoryModule {
+    @Binds fun bindFake(fake: FakeDarkIconRepository): DarkIconRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 197873f..288dcfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -186,7 +186,7 @@
     }
 
     @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         return false;
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
new file mode 100644
index 0000000..db5eaff
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -0,0 +1,89 @@
+/*
+ * 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.util.settings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FakeGlobalSettings implements GlobalSettings {
+    private final Map<String, String> mValues = new HashMap<>();
+    private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
+
+    public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global");
+
+    public FakeGlobalSettings() {
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return null;
+    }
+
+    @Override
+    public void registerContentObserver(Uri uri, boolean notifyDescendants,
+            ContentObserver settingsObserver) {
+        List<ContentObserver> observers;
+        mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
+        observers = mContentObserversAllUsers.get(uri.toString());
+        observers.add(settingsObserver);
+    }
+
+    @Override
+    public void unregisterContentObserver(ContentObserver settingsObserver) {
+        for (Map.Entry<String, List<ContentObserver>> entry :
+                mContentObserversAllUsers.entrySet()) {
+            entry.getValue().remove(settingsObserver);
+        }
+    }
+
+    @Override
+    public Uri getUriFor(String name) {
+        return Uri.withAppendedPath(CONTENT_URI, name);
+    }
+
+    @Override
+    public String getString(String name) {
+        return mValues.get(getUriFor(name).toString());
+    }
+
+    @Override
+    public boolean putString(String name, String value) {
+        return putString(name, value, null, false);
+    }
+
+    @Override
+    public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault) {
+        String key = getUriFor(name).toString();
+        mValues.put(key, value);
+
+        Uri uri = getUriFor(name);
+        for (ContentObserver observer :
+                mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
+            observer.dispatchChange(false, List.of(uri), 0);
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index 4b97316..a491886 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -23,6 +23,8 @@
 import android.os.UserHandle;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
@@ -30,7 +32,7 @@
 import java.util.List;
 import java.util.Map;
 
-public class FakeSettings implements SecureSettings, GlobalSettings, SystemSettings {
+public class FakeSettings implements SecureSettings, SystemSettings {
     private final Map<SettingsKey, String> mValues = new HashMap<>();
     private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
             new HashMap<>();
@@ -64,7 +66,7 @@
     }
 
     @Override
-    public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
+    public void registerContentObserverForUser(Uri uri, boolean notifyDescendants,
             ContentObserver settingsObserver, int userHandle) {
         List<ContentObserver> observers;
         if (userHandle == UserHandle.USER_ALL) {
@@ -147,7 +149,7 @@
     }
 
     @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
         return putString(name, value);
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index eaa109d..209cac6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -25,6 +25,7 @@
 
 public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
         implements BatteryController {
+    private boolean mIsAodPowerSave = false;
     private boolean mWirelessCharging;
 
     public FakeBatteryController(LeakCheck test) {
@@ -63,7 +64,7 @@
 
     @Override
     public boolean isAodPowerSave() {
-        return false;
+        return mIsAodPowerSave;
     }
 
     @Override
@@ -71,6 +72,10 @@
         return mWirelessCharging;
     }
 
+    public void setIsAodPowerSave(boolean isAodPowerSave) {
+        mIsAodPowerSave = isAodPowerSave;
+    }
+
     public void setWirelessCharging(boolean wirelessCharging) {
         mWirelessCharging = wirelessCharging;
     }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 5ffc094..7473ca6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateRepository
+import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -55,6 +57,12 @@
     fun unfoldKeyguardVisibilityManager(
         impl: UnfoldKeyguardVisibilityManagerImpl
     ): UnfoldKeyguardVisibilityManager = impl
+
+    @Provides
+    @Singleton
+    fun foldStateRepository(
+            impl: FoldStateRepositoryImpl
+    ): FoldStateRepository = impl
 }
 
 /**
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 6743515..003013e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -17,7 +17,6 @@
 
 import android.content.Context
 import android.os.Handler
-import android.os.Trace
 import android.util.Log
 import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
@@ -130,7 +129,6 @@
                     "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
             )
         }
-        Trace.setCounter("DeviceFoldStateProvider#onHingeAngle", angle.toLong())
 
         val currentDirection =
                 if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
index 6e87bee..ea6786e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
@@ -20,7 +20,7 @@
     fun registerCallback(callback: FoldCallback, executor: Executor)
     fun unregisterCallback(callback: FoldCallback)
 
-    interface FoldCallback {
+    fun interface FoldCallback {
         fun onFoldUpdated(isFolded: Boolean)
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
new file mode 100644
index 0000000..61b0b40
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.unfold.updates
+
+import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import javax.inject.Inject
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
+ * start folding/unfolding, screen availability
+ */
+interface FoldStateRepository {
+    /** Latest fold update, as described by [FoldStateProvider.FoldUpdate]. */
+    val foldUpdate: Flow<FoldUpdate>
+
+    /** Provides the hinge angle while the fold/unfold is in progress. */
+    val hingeAngle: Flow<Float>
+
+    enum class FoldUpdate {
+        START_OPENING,
+        START_CLOSING,
+        FINISH_HALF_OPEN,
+        FINISH_FULL_OPEN,
+        FINISH_CLOSED;
+
+        companion object {
+            /** Maps the old [FoldStateProvider.FoldUpdate] to [FoldStateRepository.FoldUpdate]. */
+            fun fromFoldUpdateId(@FoldStateProvider.FoldUpdate oldId: Int): FoldUpdate {
+                return when (oldId) {
+                    FOLD_UPDATE_START_OPENING -> START_OPENING
+                    FOLD_UPDATE_START_CLOSING -> START_CLOSING
+                    FOLD_UPDATE_FINISH_HALF_OPEN -> FINISH_HALF_OPEN
+                    FOLD_UPDATE_FINISH_FULL_OPEN -> FINISH_FULL_OPEN
+                    FOLD_UPDATE_FINISH_CLOSED -> FINISH_CLOSED
+                    else -> error("FoldUpdateNotFound")
+                }
+            }
+        }
+    }
+}
+
+class FoldStateRepositoryImpl
+@Inject
+constructor(
+    private val foldStateProvider: FoldStateProvider,
+) : FoldStateRepository {
+
+    override val hingeAngle: Flow<Float>
+        get() =
+            callbackFlow {
+                    val callback =
+                        object : FoldStateProvider.FoldUpdatesListener {
+                            override fun onHingeAngleUpdate(angle: Float) {
+                                trySend(angle)
+                            }
+                        }
+                    foldStateProvider.addCallback(callback)
+                    awaitClose { foldStateProvider.removeCallback(callback) }
+                }
+                .buffer(capacity = Channel.CONFLATED)
+
+    override val foldUpdate: Flow<FoldUpdate>
+        get() =
+            callbackFlow {
+                    val callback =
+                        object : FoldStateProvider.FoldUpdatesListener {
+                            override fun onFoldUpdate(update: Int) {
+                                trySend(FoldUpdate.fromFoldUpdateId(update))
+                            }
+                        }
+                    foldStateProvider.addCallback(callback)
+                    awaitClose { foldStateProvider.removeCallback(callback) }
+                }
+                .buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 3406102..98421a9 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -367,11 +367,7 @@
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
             mSystemHasLiveComponent = wpService != null;
 
-            ComponentName kwpService = null;
-            boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
-            if (lockscreenLiveWallpaper) {
-                kwpService = parseWallpaperComponent(infoStage, "kwp");
-            }
+            ComponentName kwpService = parseWallpaperComponent(infoStage, "kwp");
             mLockHasLiveComponent = kwpService != null;
             boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
 
@@ -381,17 +377,16 @@
             // It is valid for the imagery to be absent; it means that we were not permitted
             // to back up the original image on the source device, or there was no user-supplied
             // wallpaper image present.
-            if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
             if (lockImageStageExists) {
                 restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
             }
-            if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+            restoreFromStage(imageStage, infoStage, "wp", sysWhich);
 
             // And reset to the wallpaper service we should be using
-            if (lockscreenLiveWallpaper && mLockHasLiveComponent) {
-                updateWallpaperComponent(kwpService, false, FLAG_LOCK);
+            if (mLockHasLiveComponent) {
+                updateWallpaperComponent(kwpService, FLAG_LOCK);
             }
-            updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich);
+            updateWallpaperComponent(wpService, sysWhich);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
             mEventLogger.onRestoreException(e);
@@ -410,36 +405,24 @@
     }
 
     @VisibleForTesting
-    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)
+    void updateWallpaperComponent(ComponentName wpService, int which)
             throws IOException {
-        boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
         if (servicePackageExists(wpService)) {
             Slog.i(TAG, "Using wallpaper service " + wpService);
-            if (lockscreenLiveWallpaper) {
-                mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
-                if ((which & FLAG_LOCK) != 0) {
-                    mEventLogger.onLockLiveWallpaperRestored(wpService);
-                }
-                if ((which & FLAG_SYSTEM) != 0) {
-                    mEventLogger.onSystemLiveWallpaperRestored(wpService);
-                }
-                return;
-            }
-            mWallpaperManager.setWallpaperComponent(wpService);
-            if (applyToLock) {
-                // We have a live wallpaper and no static lock image,
-                // allow live wallpaper to show "through" on lock screen.
-                mWallpaperManager.clear(FLAG_LOCK);
+            mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
+            if ((which & FLAG_LOCK) != 0) {
                 mEventLogger.onLockLiveWallpaperRestored(wpService);
             }
-            mEventLogger.onSystemLiveWallpaperRestored(wpService);
+            if ((which & FLAG_SYSTEM) != 0) {
+                mEventLogger.onSystemLiveWallpaperRestored(wpService);
+            }
         } else {
             // If we've restored a live wallpaper, but the component doesn't exist,
             // we should log it as an error so we can easily identify the problem
             // in reports from users
             if (wpService != null) {
                 // TODO(b/268471749): Handle delayed case
-                applyComponentAtInstall(wpService, applyToLock, which);
+                applyComponentAtInstall(wpService, which);
                 Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
                         + " Will try to apply later");
             }
@@ -579,21 +562,17 @@
         // Intentionally blank
     }
 
-    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock,
-            int which) {
+    private void applyComponentAtInstall(ComponentName componentName, int which) {
         PackageMonitor packageMonitor = getWallpaperPackageMonitor(
-                componentName, applyToLock, which);
+                componentName, which);
         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
     }
 
     @VisibleForTesting
-    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock,
-            int which) {
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
         return new PackageMonitor() {
             @Override
             public void onPackageAdded(String packageName, int uid) {
-                boolean lockscreenLiveWallpaper =
-                        mWallpaperManager.isLockscreenLiveWallpaperEnabled();
                 if (!isDeviceInRestore()) {
                     // We don't want to reapply the wallpaper outside a restore.
                     unregister();
@@ -601,9 +580,11 @@
                     // We have finished restore and not succeeded, so let's log that as an error.
                     WallpaperEventLogger logger = new WallpaperEventLogger(
                             mBackupManager.getDelayedRestoreLogger());
-                    logger.onSystemLiveWallpaperRestoreFailed(
-                            WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
-                    if (applyToLock) {
+                    if ((which & FLAG_SYSTEM) != 0) {
+                        logger.onSystemLiveWallpaperRestoreFailed(
+                                WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
+                    }
+                    if ((which & FLAG_LOCK) != 0) {
                         logger.onLockLiveWallpaperRestoreFailed(
                                 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
                     }
@@ -614,37 +595,27 @@
 
                 if (componentName.getPackageName().equals(packageName)) {
                     Slog.d(TAG, "Applying component " + componentName);
-                    boolean success = lockscreenLiveWallpaper
-                            ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which)
-                            : mWallpaperManager.setWallpaperComponent(componentName);
+                    boolean success = mWallpaperManager.setWallpaperComponentWithFlags(
+                            componentName, which);
                     WallpaperEventLogger logger = new WallpaperEventLogger(
                             mBackupManager.getDelayedRestoreLogger());
                     if (success) {
-                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                        if ((which & FLAG_SYSTEM) != 0) {
                             logger.onSystemLiveWallpaperRestored(componentName);
                         }
-                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                        if ((which & FLAG_LOCK) != 0) {
                             logger.onLockLiveWallpaperRestored(componentName);
                         }
                     } else {
-                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                        if ((which & FLAG_SYSTEM) != 0) {
                             logger.onSystemLiveWallpaperRestoreFailed(
                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
                         }
-                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                        if ((which & FLAG_LOCK) != 0) {
                             logger.onLockLiveWallpaperRestoreFailed(
                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
                         }
                     }
-                    if (applyToLock && !lockscreenLiveWallpaper) {
-                        try {
-                            mWallpaperManager.clear(FLAG_LOCK);
-                            logger.onLockLiveWallpaperRestored(componentName);
-                        } catch (IOException e) {
-                            Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
-                            logger.onLockLiveWallpaperRestoreFailed(e.getClass().getName());
-                        }
-                    }
                     // We're only expecting to restore the wallpaper component once.
                     unregister();
                     mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index dc1126e..4c224fb 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -116,8 +116,6 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        when(mWallpaperManager.isLockscreenLiveWallpaperEnabled()).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
 
@@ -363,25 +361,19 @@
     @Test
     public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
-
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            verify(mWallpaperManager, times(1))
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
-            verify(mWallpaperManager, never()).clear(anyInt());
-        } else {
-            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-            verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
-        }
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+        verify(mWallpaperManager, never()).clear(anyInt());
     }
 
     @Test
@@ -390,24 +382,19 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ false, FLAG_SYSTEM);
+                /* which */ FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
 
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            verify(mWallpaperManager, times(1))
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
-            verify(mWallpaperManager, never()).clear(anyInt());
-        } else {
-            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-            verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
-        }
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never()).clear(anyInt());
     }
 
     @Test
@@ -416,7 +403,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -432,7 +419,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate "wrong" wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
@@ -622,7 +609,7 @@
     }
 
     @Test
-    public void testOnRestore_systemWallpaperImgSuccess_logsSuccess() throws Exception {
+    public void testOnRestore_wallpaperImgSuccess_logsSuccess() throws Exception {
         mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
         mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
         mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
@@ -630,17 +617,16 @@
 
         mWallpaperBackupAgent.onRestoreFinished();
 
+        // wallpaper will be applied to home & lock screen, a success for both screens in expected
         DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
                 mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
         assertThat(result).isNotNull();
         assertThat(result.getSuccessCount()).isEqualTo(1);
 
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            result = getLoggingResult(WALLPAPER_IMG_LOCK,
-                    mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
-            assertThat(result).isNotNull();
-            assertThat(result.getSuccessCount()).isEqualTo(1);
-        }
+        result = getLoggingResult(WALLPAPER_IMG_LOCK,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
     @Test
@@ -758,7 +744,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -782,7 +768,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -804,7 +790,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -938,10 +924,8 @@
         }
 
         @Override
-        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
-                boolean applyToLock, int which) {
-            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(
-                    componentName, applyToLock, which);
+        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which);
             return mWallpaperPackageMonitor;
         }
 
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index ced97cc..7ac7859 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -90,8 +90,14 @@
     // Make sound through the speaker.
     ALERT_BEEP = 2;
 
-    // Flash a notificaiton light.
+    // Flash a notification light.
     ALERT_BLINK = 4;
+
+    // Alert was attenuated by polite notif. feature.
+    ALERT_POLITE = 8;
+
+    // Alert was muted by polite notif. feature.
+    ALERT_MUTED = 16;
   }
 
   // Reasons that a notification might be dismissed.
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 21d0979..b403a7f 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -404,5 +404,9 @@
 
     // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
     NOTE_CSD_LOWER_AUDIO = 1007;
+
+    // Notify the user about external display events related to screenshot.
+    // Package: com.android.systemui
+    NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
   }
 }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
new file mode 100644
index 0000000..91acc3d
--- /dev/null
+++ b/ravenwood/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "ravenwood-annotations",
+    srcs: [
+        "annotations-src/**/*.java",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// File that contains the standard command line arguments to hoststubgen.
+filegroup {
+    name: "ravenwood-standard-options",
+    srcs: [
+        "ravenwood-standard-options.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+java_library {
+    name: "ravenwood-annotations-lib",
+    srcs: [":ravenwood-annotations"],
+    sdk_version: "core_current",
+    host_supported: true,
+    visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS
new file mode 100644
index 0000000..c06b3b9
--- /dev/null
+++ b/ravenwood/OWNERS
@@ -0,0 +1,3 @@
+jsharkey@google.com
+omakoto@google.com
+jaggies@google.com
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java
new file mode 100644
index 0000000..be7b923
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.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.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"})
+ * of a callback to get a callback at the class load time.
+ *
+ * The method must be {@code public static} with a single argument that takes
+ * {@link Class}.
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodClassLoadHook {
+    String value();
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java
new file mode 100644
index 0000000..1644ffc
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ *
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodKeep {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
new file mode 100644
index 0000000..eb883e2
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.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.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodNativeSubstitutionClass {
+    String value();
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java
new file mode 100644
index 0000000..ffa1fa5
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodRemove {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java
new file mode 100644
index 0000000..6d747da
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ */
+@Target({METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodSubstitute {
+    // TODO We should add "_host" as default. We're not doing it yet, because extractign the default
+    // value with ASM doesn't seem trivial. (? not sure.)
+    String suffix();
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java
new file mode 100644
index 0000000..a329d84
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ * TODO: Create "whole-class-throw"?
+ */
+@Target({METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodThrow {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java
new file mode 100644
index 0000000..ae6f42d
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ * TODO: Create "whole-class-throw"?
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodWholeClassKeep {
+}
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
new file mode 100644
index 0000000..6e1384f
--- /dev/null
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -0,0 +1,37 @@
+# File containing standard options to HostStubGen for Ravenwood
+
+--debug
+
+# Keep all classes / methods / fields, but make the methods throw.
+--default-throw
+
+# Uncomment below lines to enable each feature.
+# --enable-non-stub-method-check
+
+#--default-method-call-hook
+#    com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+#--default-class-load-hook
+#    com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+
+# Standard annotations.
+# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
+--keep-annotation
+    android.ravenwood.annotations.RavenwoodKeep
+
+--keep-class-annotation
+    android.ravenwood.annotations.RavenwoodWholeClassKeep
+
+--throw-annotation
+    android.ravenwood.annotations.RavenwoodThrow
+
+--remove-annotation
+    android.ravenwood.annotations.RavenwoodRemove
+
+--substitute-annotation
+    android.ravenwood.annotations.RavenwoodSubstitute
+
+--native-substitute-annotation
+    android.ravenwood.annotations.RavenwoodNativeSubstitutionClass
+
+--class-load-hook-annotation
+    android.ravenwood.annotations.RavenwoodClassLoadHook
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 1866a99..67c2caa 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -16,9 +16,12 @@
 
 package android.renderscript;
 
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.res.Resources;
+import android.util.Slog;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -35,6 +38,15 @@
     private static final String TAG = "ScriptC";
 
     /**
+     * In targetSdkVersion 35 and above, Renderscript's ScriptC stops being supported
+     * and an exception is thrown when the class is instantiated.
+     * In targetSdkVersion 34 and below, Renderscript's ScriptC still works.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = 35)
+    private static final long RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID = 297019750L;
+
+    /**
      * Only intended for use by the generated derived classes.
      *
      * @param id
@@ -89,7 +101,19 @@
         setID(id);
     }
 
+    private static void throwExceptionIfSDKTooHigh() {
+        String message =
+                "ScriptC scripts are not supported when targeting an API Level >= 35. Please refer "
+                    + "to https://developer.android.com/guide/topics/renderscript/migration-guide "
+                    + "for proposed alternatives.";
+        Slog.w(TAG, message);
+        if (CompatChanges.isChangeEnabled(RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID)) {
+            throw new UnsupportedOperationException(message);
+        }
+    }
+
     private static synchronized long internalCreate(RenderScript rs, Resources resources, int resourceID) {
+        throwExceptionIfSDKTooHigh();
         byte[] pgm;
         int pgmLength;
         InputStream is = resources.openRawResource(resourceID);
@@ -126,6 +150,7 @@
 
     private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
         //        Log.v(TAG, "Create script for resource = " + resName);
+        throwExceptionIfSDKTooHigh();
         return rs.nScriptCCreate(resName, RenderScript.getCachePath(), bitcode, bitcode.length);
     }
 }
diff --git a/services/Android.bp b/services/Android.bp
index 3ae9360..aca8409 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -238,12 +238,14 @@
 stubs_defaults {
     name: "services-stubs-default",
     installable: false,
-    args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" +
-        " --hide-annotation android.annotation.Hide" +
-        " --hide InternalClasses" + // com.android.* classes are okay in this interface
+    flags: [
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)",
+        "--hide-annotation android.annotation.Hide",
+        "--hide InternalClasses", // com.android.* classes are okay in this interface
         // TODO: remove the --hide options below
-        " --hide DeprecationMismatch" +
-        " --hide HiddenTypedefConstant",
+        "--hide DeprecationMismatch",
+        "--hide HiddenTypedefConstant",
+    ],
     visibility: ["//frameworks/base:__subpackages__"],
     filter_packages: ["com.android."],
 }
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 6fa9c08..75ecdb7 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,17 +1,26 @@
 package: "com.android.server.accessibility"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
-    name: "proxy_use_apps_on_virtual_device_listener"
+    name: "add_window_token_without_lock"
     namespace: "accessibility"
-    description: "Fixes race condition described in b/286587811"
-    bug: "286587811"
+    description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
+    bug: "297972548"
 }
 
 flag {
-    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
+    name: "deprecate_package_list_observer"
     namespace: "accessibility"
-    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
-    bug: "257274411"
+    description: "Stops using the deprecated PackageListObserver."
+    bug: "304561459"
+}
+
+flag {
+    name: "disable_continuous_shortcut_on_force_stop"
+    namespace: "accessibility"
+    description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+    bug: "198018180"
 }
 
 flag {
@@ -22,17 +31,10 @@
 }
 
 flag {
-    name: "send_a11y_events_based_on_state"
+    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
     namespace: "accessibility"
-    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
-    bug: "295575684"
-}
-
-flag {
-    name: "add_window_token_without_lock"
-    namespace: "accessibility"
-    description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
-    bug: "297972548"
+    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
+    bug: "257274411"
 }
 
 flag {
@@ -40,4 +42,32 @@
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
     bug: "295327792"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "proxy_use_apps_on_virtual_device_listener"
+    namespace: "accessibility"
+    description: "Fixes race condition described in b/286587811"
+    bug: "286587811"
+}
+
+flag {
+    name: "reduce_touch_exploration_sensitivity"
+    namespace: "accessibility"
+    description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
+    bug: "303677860"
+}
+
+flag {
+    name: "scan_packages_without_lock"
+    namespace: "accessibility"
+    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+    bug: "295969873"
+}
+
+flag {
+    name: "send_a11y_events_based_on_state"
+    namespace: "accessibility"
+    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
+    bug: "295575684"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cd83f8f..2913716 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -68,7 +68,7 @@
      *
      * @see #setUserAndEnabledFeatures(int, int)
      */
-    static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
+    static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP = 0x00000001;
 
     /**
      * Flag for enabling the touch exploration feature.
@@ -100,7 +100,7 @@
 
     /**
      * Flag for enabling the feature to control the screen magnifier. If
-     * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored
+     * {@link #FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP} is set this flag is ignored
      * as the screen magnifier feature performs a super set of the work
      * performed by this feature.
      *
@@ -145,11 +145,18 @@
     /** Flag for intercepting generic motion events. */
     static final int FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS = 0x00000800;
 
+    /**
+     * Flag for enabling the two-finger triple-tap magnification feature.
+     *
+     * @see #setUserAndEnabledFeatures(int, int)
+     */
+    static final int FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP = 0x00001000;
+
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
                     | FLAG_FEATURE_TOUCH_EXPLORATION
-                    | FLAG_FEATURE_SCREEN_MAGNIFIER
+                    | FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
                     | FLAG_SERVICE_HANDLES_DOUBLE_TAP
                     | FLAG_REQUEST_MULTI_FINGER_GESTURES
@@ -530,7 +537,7 @@
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
-                || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+                || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0)
                 || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
             final MagnificationGestureHandler magnificationGestureHandler =
                     createMagnificationGestureHandler(displayId,
@@ -648,7 +655,7 @@
     private MagnificationGestureHandler createMagnificationGestureHandler(
             int displayId, Context displayContext) {
         final boolean detectControlGestures = (mEnabledFeatures
-                & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+                & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0;
         final boolean triggerable = (mEnabledFeatures
                 & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
         MagnificationGestureHandler magnificationGestureHandler;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 60d4ee6..a7b5325 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
@@ -63,7 +64,6 @@
 import android.app.RemoteAction;
 import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetManagerInternal;
-import android.companion.virtual.VirtualDeviceManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -182,6 +182,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
@@ -290,14 +291,13 @@
 
     private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
 
-    private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
-            new ArrayList<>();
-
     private final IntArray mTempIntArray = new IntArray(0);
 
     private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
             new RemoteCallbackList<>();
 
+    private PackageMonitor mPackageMonitor;
+
     @VisibleForTesting
     final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
 
@@ -531,6 +531,19 @@
         disableAccessibilityMenuToMigrateIfNeeded();
     }
 
+    /**
+     * Returns if the current thread is holding {@link #mLock}. Used for testing
+     * deadlock bug fixes.
+     *
+     * <p><strong>Warning:</strong> this should not be used for production logic
+     * because by the time you receive an answer it may no longer be valid.
+     * </p>
+     */
+    @VisibleForTesting
+    boolean unsafeIsLockHeld() {
+        return Thread.holdsLock(mLock);
+    }
+
     @Override
     public int getCurrentUserIdLocked() {
         return mCurrentUserId;
@@ -638,6 +651,16 @@
         }
     }
 
+    /**
+     * Returns the lock object for any synchronized test blocks.
+     * Should not be used outside of testing.
+     * @return lock object.
+     */
+    @VisibleForTesting
+    Object getLock() {
+        return mLock;
+    }
+
     AccessibilityUserState getCurrentUserState() {
         synchronized (mLock) {
             return getCurrentUserStateLocked();
@@ -690,6 +713,19 @@
         }
     }
 
+    private void onSomePackagesChangedLocked(
+            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+            @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        final AccessibilityUserState userState = getCurrentUserStateLocked();
+        // Reload the installed services since some services may have different attributes
+        // or resolve info (does not support equals), etc. Remove them then to force reload.
+        userState.mInstalledServices.clear();
+        if (readConfigurationForUserStateLocked(userState,
+                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) {
+            onUserStateChangedLocked(userState);
+        }
+    }
+
     private void onPackageRemovedLocked(String packageName) {
         final AccessibilityUserState userState = getCurrentUserState();
         final Predicate<ComponentName> filter =
@@ -721,8 +757,69 @@
         }
     }
 
+    /**
+     * Handles a package or packages being force stopped.
+     * Will disable any relevant services,
+     * and remove any button targets of continuous services,
+     * denoted by {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON}.
+     * If the result is {@code true},
+     * then {@link AccessibilityManagerService#onUserStateChangedLocked(
+     * AccessibilityUserState, boolean)} should be called afterwards.
+     *
+     * @param packages list of packages that have stopped.
+     * @param userState user state to be read & modified.
+     * @return {@code true} if a service was enabled or a button target was removed,
+     * {@code false} otherwise.
+     */
+    @VisibleForTesting
+    boolean onPackagesForceStoppedLocked(
+            String[] packages, AccessibilityUserState userState) {
+        final List<String> continuousServicePackages =
+                userState.mInstalledServices.stream().filter(service ->
+                        (service.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+                                == FLAG_REQUEST_ACCESSIBILITY_BUTTON
+                ).map(service -> service.getComponentName().flattenToString()).toList();
+
+        boolean enabledServicesChanged = false;
+        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+        while (it.hasNext()) {
+            final ComponentName comp = it.next();
+            final String compPkg = comp.getPackageName();
+            for (String pkg : packages) {
+                if (compPkg.equals(pkg)) {
+                    it.remove();
+                    userState.getBindingServicesLocked().remove(comp);
+                    userState.getCrashedServicesLocked().remove(comp);
+                    enabledServicesChanged = true;
+                }
+            }
+        }
+        if (enabledServicesChanged) {
+            persistComponentNamesToSettingLocked(
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    userState.mEnabledServices, userState.mUserId);
+        }
+
+        boolean buttonTargetsChanged = userState.mAccessibilityButtonTargets.removeIf(
+                target -> continuousServicePackages.stream().anyMatch(
+                        pkg -> Objects.equals(target, pkg)));
+        if (buttonTargetsChanged) {
+            persistColonDelimitedSetToSettingLocked(
+                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                    userState.mUserId,
+                    userState.mAccessibilityButtonTargets, str -> str);
+        }
+
+        return enabledServicesChanged || buttonTargetsChanged;
+    }
+
+    @VisibleForTesting
+    PackageMonitor getPackageMonitor() {
+        return mPackageMonitor;
+    }
+
     private void registerBroadcastReceivers() {
-        PackageMonitor monitor = new PackageMonitor() {
+        mPackageMonitor = new PackageMonitor() {
             @Override
             public void onSomePackagesChanged() {
                 if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
@@ -730,13 +827,25 @@
                             FLAGS_PACKAGE_BROADCAST_RECEIVER);
                 }
 
+                final int userId = getChangingUserId();
+                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+                if (Flags.scanPackagesWithoutLock()) {
+                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+                }
                 synchronized (mLock) {
                     // Only the profile parent can install accessibility services.
                     // Therefore we ignore packages from linked profiles.
-                    if (getChangingUserId() != mCurrentUserId) {
+                    if (userId != mCurrentUserId) {
                         return;
                     }
-                    onSomePackagesChangedLocked();
+                    if (Flags.scanPackagesWithoutLock()) {
+                        onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+                                parsedAccessibilityShortcutInfos);
+                    } else {
+                        onSomePackagesChangedLocked();
+                    }
                 }
             }
 
@@ -751,8 +860,14 @@
                             FLAGS_PACKAGE_BROADCAST_RECEIVER,
                             "packageName=" + packageName + ";uid=" + uid);
                 }
+                final int userId = getChangingUserId();
+                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+                if (Flags.scanPackagesWithoutLock()) {
+                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+                }
                 synchronized (mLock) {
-                    final int userId = getChangingUserId();
                     if (userId != mCurrentUserId) {
                         return;
                     }
@@ -765,8 +880,13 @@
                     // Reloads the installed services info to make sure the rebound service could
                     // get a new one.
                     userState.mInstalledServices.clear();
-                    final boolean configurationChanged =
-                            readConfigurationForUserStateLocked(userState);
+                    final boolean configurationChanged;
+                    if (Flags.scanPackagesWithoutLock()) {
+                        configurationChanged = readConfigurationForUserStateLocked(userState,
+                                parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+                    } else {
+                        configurationChanged = readConfigurationForUserStateLocked(userState);
+                    }
                     if (reboundAService || configurationChanged) {
                         onUserStateChangedLocked(userState);
                     }
@@ -797,6 +917,16 @@
                 }
             }
 
+            /**
+             * Handles instances in which a package or packages have forcibly stopped.
+             *
+             * @param intent intent containing package event information.
+             * @param uid linux process user id (different from Android user id).
+             * @param packages array of package names that have stopped.
+             * @param doit whether to try and handle the stop or just log the trace.
+             *
+             * @return {@code true} if package should be restarted, {@code false} otherwise.
+             */
             @Override
             public boolean onHandleForceStop(Intent intent, String[] packages,
                     int uid, boolean doit) {
@@ -814,59 +944,69 @@
                         return false;
                     }
                     final AccessibilityUserState userState = getUserStateLocked(userId);
-                    final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
-                    while (it.hasNext()) {
-                        final ComponentName comp = it.next();
-                        final String compPkg = comp.getPackageName();
-                        for (String pkg : packages) {
-                            if (compPkg.equals(pkg)) {
-                                if (!doit) {
-                                    return true;
+
+                    if (Flags.disableContinuousShortcutOnForceStop()) {
+                        if (doit && onPackagesForceStoppedLocked(packages, userState)) {
+                            onUserStateChangedLocked(userState);
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    } else {
+                        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+                        while (it.hasNext()) {
+                            final ComponentName comp = it.next();
+                            final String compPkg = comp.getPackageName();
+                            for (String pkg : packages) {
+                                if (compPkg.equals(pkg)) {
+                                    if (!doit) {
+                                        return true;
+                                    }
+                                    it.remove();
+                                    userState.getBindingServicesLocked().remove(comp);
+                                    userState.getCrashedServicesLocked().remove(comp);
+                                    persistComponentNamesToSettingLocked(
+                                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                                            userState.mEnabledServices, userId);
+                                    onUserStateChangedLocked(userState);
                                 }
-                                it.remove();
-                                userState.getBindingServicesLocked().remove(comp);
-                                userState.getCrashedServicesLocked().remove(comp);
-                                persistComponentNamesToSettingLocked(
-                                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                        userState.mEnabledServices, userId);
-                                onUserStateChangedLocked(userState);
                             }
                         }
+                        return false;
                     }
-                    return false;
                 }
             }
         };
 
         // package changes
-        monitor.register(mContext, null,  UserHandle.ALL, true);
+        mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
-        // Register an additional observer for new packages using PackageManagerInternal, which
-        // generally notifies observers much sooner than the BroadcastReceiver-based PackageMonitor.
-        final PackageManagerInternal pm = LocalServices.getService(
-                PackageManagerInternal.class);
-        if (pm != null) {
-            pm.getPackageList(new PackageManagerInternal.PackageListObserver() {
-                @Override
-                public void onPackageAdded(String packageName, int uid) {
-                    final int userId = UserHandle.getUserId(uid);
-                    synchronized (mLock) {
-                        if (userId == mCurrentUserId) {
-                            onSomePackagesChangedLocked();
+        if (!Flags.deprecatePackageListObserver()) {
+            final PackageManagerInternal pm = LocalServices.getService(
+                    PackageManagerInternal.class);
+            if (pm != null) {
+                pm.getPackageList(new PackageManagerInternal.PackageListObserver() {
+                    @Override
+                    public void onPackageAdded(String packageName, int uid) {
+                        final int userId = UserHandle.getUserId(uid);
+                        synchronized (mLock) {
+                            if (userId == mCurrentUserId) {
+                                onSomePackagesChangedLocked();
+                            }
                         }
                     }
-                }
 
-                @Override
-                public void onPackageRemoved(String packageName, int uid) {
-                    final int userId = UserHandle.getUserId(uid);
-                    synchronized (mLock) {
-                        if (userId == mCurrentUserId) {
-                            onPackageRemovedLocked(packageName);
+                    @Override
+                    public void onPackageRemoved(String packageName, int uid) {
+                        final int userId = UserHandle.getUserId(uid);
+                        synchronized (mLock) {
+                            if (userId == mCurrentUserId) {
+                                onPackageRemovedLocked(packageName);
+                            }
                         }
                     }
-                }
-            });
+                });
+            }
         }
 
         // user change and unlock
@@ -930,18 +1070,7 @@
         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
                 Context.RECEIVER_EXPORTED);
 
-        if (android.companion.virtual.flags.Flags.vdmPublicApis()) {
-            VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
-            if (vdm != null) {
-                vdm.registerVirtualDeviceListener(mContext.getMainExecutor(),
-                        new VirtualDeviceManager.VirtualDeviceListener() {
-                            @Override
-                            public void onVirtualDeviceClosed(int deviceId) {
-                                mProxyManager.clearConnections(deviceId);
-                            }
-                        });
-            }
-        } else {
+        if (!android.companion.virtual.flags.Flags.vdmPublicApis()) {
             final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -1831,8 +1960,15 @@
         mA11yWindowManager.onTouchInteractionEnd();
     }
 
-    private void switchUser(int userId) {
+    @VisibleForTesting
+    void switchUser(int userId) {
         mMagnificationController.updateUserIdIfNeeded(userId);
+        List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+        List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+        if (Flags.scanPackagesWithoutLock()) {
+            parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+            parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+        }
         synchronized (mLock) {
             if (mCurrentUserId == userId && mInitialized) {
                 return;
@@ -1857,7 +1993,12 @@
             mCurrentUserId = userId;
             AccessibilityUserState userState = getCurrentUserStateLocked();
 
-            readConfigurationForUserStateLocked(userState);
+            if (Flags.scanPackagesWithoutLock()) {
+                readConfigurationForUserStateLocked(userState,
+                        parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+            } else {
+                readConfigurationForUserStateLocked(userState);
+            }
             mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
             // Even if reading did not yield change, we have to update
             // the state since the context in which the current user
@@ -2105,8 +2246,17 @@
         }
     }
 
-    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
-        mTempAccessibilityServiceInfoList.clear();
+    /**
+     * Finds packages that provide AccessibilityService interfaces, and parses
+     * their metadata XML to build up {@link AccessibilityServiceInfo} objects.
+     *
+     * <p>
+     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+     * stall, so this method should not be called while holding a lock.
+     * </p>
+     */
+    private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) {
+        List<AccessibilityServiceInfo> result = new ArrayList<>();
 
         int flags = PackageManager.GET_SERVICES
                 | PackageManager.GET_META_DATA
@@ -2114,12 +2264,14 @@
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-        if (userState.getBindInstantServiceAllowedLocked()) {
-            flags |= PackageManager.MATCH_INSTANT;
+        synchronized (mLock) {
+            if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) {
+                flags |= PackageManager.MATCH_INSTANT;
+            }
         }
 
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
-                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
+                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId);
 
         for (int i = 0, count = installedServices.size(); i < count; i++) {
             ResolveInfo resolveInfo = installedServices.get(i);
@@ -2132,40 +2284,60 @@
             AccessibilityServiceInfo accessibilityServiceInfo;
             try {
                 accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
-                if (!accessibilityServiceInfo.isWithinParcelableSize()) {
-                    Slog.e(LOG_TAG, "Skipping service "
-                            + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
-                            + " because service info size is larger than safe parcelable limits.");
-                    continue;
-                }
-                if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
-                    // Restore the crashed attribute.
-                    accessibilityServiceInfo.crashed = true;
-                }
-                mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
             } catch (XmlPullParserException | IOException xppe) {
                 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
+                continue;
+            }
+            if (!accessibilityServiceInfo.isWithinParcelableSize()) {
+                Slog.e(LOG_TAG, "Skipping service "
+                        + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
+                        + " because service info size is larger than safe parcelable limits.");
+                continue;
+            }
+            result.add(accessibilityServiceInfo);
+        }
+        return result;
+    }
+
+    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState,
+            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) {
+        for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) {
+            AccessibilityServiceInfo accessibilityServiceInfo =
+                    parsedAccessibilityServiceInfos.get(i);
+            if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) {
+                // Restore the crashed attribute.
+                accessibilityServiceInfo.crashed = true;
             }
         }
 
-        if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+        if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
             userState.mInstalledServices.clear();
-            userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
-            mTempAccessibilityServiceInfoList.clear();
+            userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
             return true;
         }
-
-        mTempAccessibilityServiceInfoList.clear();
         return false;
     }
 
-    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
-        final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
-                .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
-                        mContext, mCurrentUserId);
-        if (!shortcutInfos.equals(userState.mInstalledShortcuts)) {
+    /**
+     * Returns the {@link AccessibilityShortcutInfo}s of the installed
+     * accessibility shortcut targets for the given user.
+     *
+     * <p>
+     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+     * stall, so this method should not be called while holding a lock.
+     * </p>
+     */
+    private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) {
+        // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager.
+        return AccessibilityManager.getInstance(mContext)
+                .getInstalledAccessibilityShortcutListAsUser(mContext, userId);
+    }
+
+    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
             userState.mInstalledShortcuts.clear();
-            userState.mInstalledShortcuts.addAll(shortcutInfos);
+            userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
             return true;
         }
         return false;
@@ -2356,7 +2528,8 @@
      * @param userId The user id.
      * @param outComponentNames The output component names.
      */
-    private void readComponentNamesFromSettingLocked(String settingName, int userId,
+    @VisibleForTesting
+    void readComponentNamesFromSettingLocked(String settingName, int userId,
             Set<ComponentName> outComponentNames) {
         readColonDelimitedSettingToSet(settingName, userId,
                 str -> ComponentName.unflattenFromString(str), outComponentNames);
@@ -2385,7 +2558,19 @@
                 componentName -> componentName.flattenToShortString());
     }
 
-    private <T> void readColonDelimitedSettingToSet(String settingName, int userId,
+    /**
+     * Reads a colon delimited setting,
+     * passes the values through a function,
+     * then stores the values in a provided set.
+     *
+     * @param settingName Name of setting.
+     * @param userId user id corresponding to setting.
+     * @param toItem function mapping values to the output set.
+     * @param outSet output set to write to.
+     * @param <T> type of output set.
+     */
+    @VisibleForTesting
+    <T> void readColonDelimitedSettingToSet(String settingName, int userId,
             Function<String, T> toItem, Set<T> outSet) {
         final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 settingName, userId);
@@ -2597,8 +2782,15 @@
         AccessibilityInputFilter inputFilter = null;
         synchronized (mLock) {
             int flags = 0;
-            if (userState.isDisplayMagnificationEnabledLocked()) {
-                flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
+            if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) {
+                flags |= AccessibilityInputFilter
+                        .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP;
+            }
+            if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+                if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) {
+                    flags |= AccessibilityInputFilter
+                            .FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP;
+                }
             }
             if (userState.isShortcutMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
@@ -2890,9 +3082,23 @@
         userState.setFilterKeyEventsEnabledLocked(false);
     }
 
+    // ErrorProne doesn't understand that this method is only called while locked,
+    // returning an error for accessing mCurrentUserId.
+    @SuppressWarnings("GuardedBy")
     private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
-        boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
-        somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
+        return readConfigurationForUserStateLocked(userState,
+                parseAccessibilityServiceInfos(mCurrentUserId),
+                parseAccessibilityShortcutInfos(mCurrentUserId));
+    }
+
+    private boolean readConfigurationForUserStateLocked(
+            AccessibilityUserState userState,
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        boolean somethingChanged = readInstalledAccessibilityServiceLocked(
+                userState, parsedAccessibilityServiceInfos);
+        somethingChanged |= readInstalledAccessibilityShortcutLocked(
+                userState, parsedAccessibilityShortcutInfos);
         somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
         somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
         somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
@@ -2937,12 +3143,29 @@
     }
 
     private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) {
-        final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
+        final boolean magnificationSingleFingerTripleTapEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                 0, userState.mUserId) == 1;
-        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
-            userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
+        if ((magnificationSingleFingerTripleTapEnabled
+                != userState.isMagnificationSingleFingerTripleTapEnabledLocked())) {
+            userState.setMagnificationSingleFingerTripleTapEnabledLocked(
+                    magnificationSingleFingerTripleTapEnabled);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean readMagnificationTwoFingerTripleTapSettingsLocked(
+            AccessibilityUserState userState) {
+        final boolean magnificationTwoFingerTripleTapEnabled = Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                0, userState.mUserId) == 1;
+        if ((magnificationTwoFingerTripleTapEnabled
+                != userState.isMagnificationTwoFingerTripleTapEnabledLocked())) {
+            userState.setMagnificationTwoFingerTripleTapEnabledLocked(
+                    magnificationTwoFingerTripleTapEnabled);
             return true;
         }
         return false;
@@ -3182,7 +3405,8 @@
         // We would skip overlay display because it uses overlay window to simulate secondary
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
-        if (userState.isDisplayMagnificationEnabledLocked()
+        if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+                || userState.isMagnificationTwoFingerTripleTapEnabledLocked()
                 || userState.isShortcutMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
@@ -3211,7 +3435,7 @@
             return;
         }
         final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
-                || userState.isDisplayMagnificationEnabledLocked())
+                || userState.isMagnificationSingleFingerTripleTapEnabledLocked())
                 && (userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
                 || userHasMagnificationServicesLocked(userState);
@@ -3362,7 +3586,7 @@
                 return true;
             }
             final boolean requestA11yButton = (serviceInfo.flags
-                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+                    & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
                 // An a11y service targeting sdk version > Q and request A11y button and is assigned
                 // to a11y btn should be in the enabled list.
@@ -3663,7 +3887,7 @@
             final int targetSdk = installedServiceInfo.getResolveInfo()
                     .serviceInfo.applicationInfo.targetSdkVersion;
             final boolean requestA11yButton = (installedServiceInfo.flags
-                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+                    & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             // Turns on / off the accessibility service
             if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
                     || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
@@ -4715,8 +4939,11 @@
         private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.TOUCH_EXPLORATION_ENABLED);
 
-        private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+        private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure
+                .getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+
+        private final Uri mMagnificationTwoFingerTripleTapEnabledUri = Settings.Secure.getUriFor(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED);
 
         private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -4773,8 +5000,12 @@
         public void register(ContentResolver contentResolver) {
             contentResolver.registerContentObserver(mTouchExplorationEnabledUri,
                     false, this, UserHandle.USER_ALL);
-            contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
+            contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri,
                     false, this, UserHandle.USER_ALL);
+            if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+                contentResolver.registerContentObserver(mMagnificationTwoFingerTripleTapEnabledUri,
+                        false, this, UserHandle.USER_ALL);
+            }
             contentResolver.registerContentObserver(mAutoclickEnabledUri,
                     false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -4821,10 +5052,15 @@
                     if (readTouchExplorationEnabledSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
-                } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
+                } else if (mMagnificationmSingleFingerTripleTapEnabledUri.equals(uri)) {
                     if (readMagnificationEnabledSettingsLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
+                } else if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                        && mMagnificationTwoFingerTripleTapEnabledUri.equals(uri)) {
+                    if (readMagnificationTwoFingerTripleTapSettingsLocked(userState)) {
+                        onUserStateChangedLocked(userState);
+                    }
                 } else if (mAutoclickEnabledUri.equals(uri)) {
                     if (readAutoclickEnabledSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
@@ -4896,7 +5132,7 @@
         updateWindowMagnificationConnectionIfNeeded(userState);
         // Remove magnification button UI when the magnification capability is not all mode or
         // magnification is disabled.
-        if (!(userState.isDisplayMagnificationEnabledLocked()
+        if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
                 || userState.isShortcutMagnificationEnabledLocked())
                 || userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 693526a..68ee780 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,7 +109,8 @@
     private boolean mBindInstantServiceAllowed;
     private boolean mIsAudioDescriptionByDefaultRequested;
     private boolean mIsAutoclickEnabled;
-    private boolean mIsDisplayMagnificationEnabled;
+    private boolean mIsMagnificationSingleFingerTripleTapEnabled;
+    private boolean mMagnificationTwoFingerTripleTapEnabled;
     private boolean mIsFilterKeyEventsEnabled;
     private boolean mIsPerformGesturesEnabled;
     private boolean mAccessibilityFocusOnlyInActiveWindow;
@@ -211,7 +212,8 @@
         mRequestMultiFingerGestures = false;
         mRequestTwoFingerPassthrough = false;
         mSendMotionEventsEnabled = false;
-        mIsDisplayMagnificationEnabled = false;
+        mIsMagnificationSingleFingerTripleTapEnabled = false;
+        mMagnificationTwoFingerTripleTapEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
         mUserInteractiveUiTimeout = 0;
@@ -520,7 +522,7 @@
                 .append(String.valueOf(mRequestTwoFingerPassthrough));
         pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
-                mIsDisplayMagnificationEnabled));
+                mIsMagnificationSingleFingerTripleTapEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
         pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
         pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -625,12 +627,20 @@
         mIsAutoclickEnabled = enabled;
     }
 
-    public boolean isDisplayMagnificationEnabledLocked() {
-        return mIsDisplayMagnificationEnabled;
+    public boolean isMagnificationSingleFingerTripleTapEnabledLocked() {
+        return mIsMagnificationSingleFingerTripleTapEnabled;
     }
 
-    public void setDisplayMagnificationEnabledLocked(boolean enabled) {
-        mIsDisplayMagnificationEnabled = enabled;
+    public void setMagnificationSingleFingerTripleTapEnabledLocked(boolean enabled) {
+        mIsMagnificationSingleFingerTripleTapEnabled = enabled;
+    }
+
+    public boolean isMagnificationTwoFingerTripleTapEnabledLocked() {
+        return mMagnificationTwoFingerTripleTapEnabled;
+    }
+
+    public void setMagnificationTwoFingerTripleTapEnabledLocked(boolean enabled) {
+        mMagnificationTwoFingerTripleTapEnabled = enabled;
     }
 
     public boolean isFilterKeyEventsEnabledLocked() {
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index ed77476..2032a50 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -101,6 +101,8 @@
     private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
             mAppsOnVirtualDeviceListener;
 
+    private VirtualDeviceManager.VirtualDeviceListener mVirtualDeviceListener;
+
     /**
      * Callbacks into AccessibilityManagerService.
      */
@@ -189,6 +191,9 @@
                     }
                 }
             }
+            if (mProxyA11yServiceConnections.size() == 1) {
+                registerVirtualDeviceListener();
+            }
         }
 
         // If the client dies, make sure to remove the connection.
@@ -210,6 +215,31 @@
         connection.initializeServiceInterface(client);
     }
 
+    private void registerVirtualDeviceListener() {
+        VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+        if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) {
+            return;
+        }
+        if (mVirtualDeviceListener == null) {
+            mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {
+                @Override
+                public void onVirtualDeviceClosed(int deviceId) {
+                    clearConnections(deviceId);
+                }
+            };
+        }
+
+        vdm.registerVirtualDeviceListener(mContext.getMainExecutor(), mVirtualDeviceListener);
+    }
+
+    private void unregisterVirtualDeviceListener() {
+        VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+        if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) {
+            return;
+        }
+        vdm.unregisterVirtualDeviceListener(mVirtualDeviceListener);
+    }
+
     /**
      * Unregister the proxy based on display id.
      */
@@ -258,6 +288,9 @@
                 deviceId = mProxyA11yServiceConnections.get(displayId).getDeviceId();
                 mProxyA11yServiceConnections.remove(displayId);
                 removedFromConnections = true;
+                if (mProxyA11yServiceConnections.size() == 0) {
+                    unregisterVirtualDeviceListener();
+                }
             }
         }
 
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..fc8d4f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -882,10 +882,22 @@
         final int pointerIndex = event.findPointerIndex(pointerId);
         switch (event.getPointerCount()) {
             case 1:
-            // Touch exploration.
+                // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                mDispatcher.sendMotionEvent(
-                        event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                if (Flags.reduceTouchExplorationSensitivity()
+                        && mState.getLastInjectedHoverEvent() != null) {
+                    final MotionEvent lastEvent = mState.getLastInjectedHoverEvent();
+                    final float deltaX = lastEvent.getX() - rawEvent.getX();
+                    final float deltaY = lastEvent.getY() - rawEvent.getY();
+                    final double moveDelta = Math.hypot(deltaX, deltaY);
+                    if (moveDelta > mTouchSlop) {
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                    }
+                } else {
+                    mDispatcher.sendMotionEvent(
+                            event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                }
                 break;
             case 2:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 30b9d0b..01064ac 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -873,7 +873,7 @@
 
                         transitionToDelegatingStateAndClear();
 
-                    } else if (mDetectTripleTap
+                    } else if (mDetectSingleFingerTripleTap
                             // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
@@ -989,7 +989,7 @@
             // Shortcut acts as the 2 initial taps
             if (mShortcutTriggered) return tapCount() + 2 >= numTaps;
 
-            final boolean multitapTriggered = mDetectTripleTap
+            final boolean multitapTriggered = mDetectSingleFingerTripleTap
                     && tapCount() >= numTaps
                     && isMultiTap(mPreLastDown, mLastDown)
                     && isMultiTap(mPreLastUp, mLastUp);
@@ -1205,7 +1205,7 @@
          * @return true if tap is out of distance slop
          */
         boolean isTapOutOfDistanceSlop() {
-            if (!mDetectTripleTap) return false;
+            if (!mDetectSingleFingerTripleTap) return false;
             if (mPreLastDown == null || mLastDown == null) {
                 return false;
             }
@@ -1282,7 +1282,7 @@
                 + ", mMagnifiedInteractionState=" + mPanningScalingState
                 + ", mViewportDraggingState=" + mViewportDraggingState
                 + ", mSinglePanningState=" + mSinglePanningState
-                + ", mDetectTripleTap=" + mDetectTripleTap
+                + ", mDetectSingleFingerTripleTap=" + mDetectSingleFingerTripleTap
                 + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger
                 + ", mCurrentState=" + State.nameOf(mCurrentState)
                 + ", mPreviousState=" + State.nameOf(mPreviousState)
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 2894693..8476a5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -57,11 +57,11 @@
     protected final boolean mDetectShortcutTrigger;
 
     /**
-     * {@code true} if this detector should detect and respond to triple-tap
+     * {@code true} if this detector should detect and respond to single-finger triple-tap
      * gestures for engaging and disengaging magnification,
      * {@code false} if it should ignore such gestures
      */
-    protected final boolean mDetectTripleTap;
+    protected final boolean mDetectSingleFingerTripleTap;
 
     /** Callback interface to report that magnification is interactive with a user. */
     public interface Callback {
@@ -85,12 +85,12 @@
     private final AccessibilityTraceManager mTrace;
     protected final Callback mCallback;
 
-    protected MagnificationGestureHandler(int displayId, boolean detectTripleTap,
+    protected MagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap,
             boolean detectShortcutTrigger,
             AccessibilityTraceManager trace,
             @NonNull Callback callback) {
         mDisplayId = displayId;
-        mDetectTripleTap = detectTripleTap;
+        mDetectSingleFingerTripleTap = detectSingleFingerTripleTap;
         mDetectShortcutTrigger = detectShortcutTrigger;
         mTrace = trace;
         mCallback = callback;
@@ -128,7 +128,7 @@
     }
 
     private boolean shouldDispatchTransformedEvent(MotionEvent event) {
-        if ((!mDetectTripleTap && !mDetectShortcutTrigger) || !event.isFromSource(
+        if ((!mDetectSingleFingerTripleTap && !mDetectShortcutTrigger) || !event.isFromSource(
                 SOURCE_TOUCHSCREEN)) {
             return true;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index c58e9a6..2d9dcb9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -111,7 +111,7 @@
                 (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent,
                         policyFlags));
         mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate);
-        mDetectingState = new DetectingState(context, mDetectTripleTap);
+        mDetectingState = new DetectingState(context);
         mViewportDraggingState = new ViewportDraggingState();
         mObservePanningScalingState = new PanningScalingGestureState(
                 new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true,
@@ -448,22 +448,14 @@
 
         private final MagnificationGesturesObserver mGesturesObserver;
 
-        /**
-         * {@code true} if this detector should detect and respond to triple-tap
-         * gestures for engaging and disengaging magnification,
-         * {@code false} if it should ignore such gestures
-         */
-        private final boolean mDetectTripleTap;
-
-        DetectingState(@UiContext Context context, boolean detectTripleTap) {
-            mDetectTripleTap = detectTripleTap;
-            final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1,
-                    mDetectTripleTap
+        DetectingState(@UiContext Context context) {
+            final MultiTap multiTap = new MultiTap(context, mDetectSingleFingerTripleTap ? 3 : 1,
+                    mDetectSingleFingerTripleTap
                             ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP
                             : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null);
             final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context,
-                    mDetectTripleTap ? 3 : 1,
-                    mDetectTripleTap
+                    mDetectSingleFingerTripleTap ? 3 : 1,
+                    mDetectSingleFingerTripleTap
                             ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD
                             : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null);
             mGesturesObserver = new MagnificationGesturesObserver(this,
@@ -488,7 +480,7 @@
         @Override
         public boolean shouldStopDetection(MotionEvent motionEvent) {
             return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)
-                    && !mDetectTripleTap;
+                    && !mDetectSingleFingerTripleTap;
         }
 
         @Override
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 123b65c..b37bbd6 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+  name: "fill_fields_from_current_session_only"
+  namespace: "autofill"
+  description: "Only fill autofill fields that are part of the current session."
+  bug: "270722825"
+}
+
+flag {
   name: "relayout"
   namespace: "autofill"
   description: "Mitigation for relayout issue"
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c7b53c5..e4f1d3a 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -58,6 +58,7 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.Flags;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -226,6 +227,12 @@
     @GuardedBy("mFlagLock")
     private int mMaxInputLengthForAutofill;
 
+    @GuardedBy("mFlagLock")
+    private boolean mAutofillCredmanIntegrationEnabled;
+
+    @GuardedBy("mFlagLock")
+    private boolean mIsFillFieldsFromCurrentSessionOnly;
+
     // Default flag values for Autofill PCC
 
     private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = "";
@@ -356,6 +363,7 @@
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:
                 case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:
                 case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK:
+                case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION:
                     setDeviceConfigProperties();
                     break;
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
@@ -701,12 +709,16 @@
                     DeviceConfig.NAMESPACE_AUTOFILL,
                     AutofillFeatureFlags.DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL,
                     AutofillFeatureFlags.DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL);
+            mAutofillCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
+            mIsFillFieldsFromCurrentSessionOnly = Flags.fillFieldsFromCurrentSessionOnly();
             if (verbose) {
                 Slog.v(mTag, "setDeviceConfigProperties() for PCC: "
                         + "mPccClassificationEnabled=" + mPccClassificationEnabled
                         + ", mPccPreferProviderOverPcc=" + mPccPreferProviderOverPcc
                         + ", mPccUseFallbackDetection=" + mPccUseFallbackDetection
-                        + ", mPccProviderHints=" + mPccProviderHints);
+                        + ", mPccProviderHints=" + mPccProviderHints
+                        + ", mAutofillCredmanIntegrationEnabled="
+                        + mAutofillCredmanIntegrationEnabled);
             }
         }
     }
@@ -965,6 +977,15 @@
     }
 
     /**
+     * Whether the Autofill-Credman integration feature flag is enabled.
+     */
+    public boolean isAutofillCredmanIntegrationEnabled() {
+        synchronized (mFlagLock) {
+            return mAutofillCredmanIntegrationEnabled;
+        }
+    }
+
+    /**
      * Whether the Autofill Provider shouldbe preferred over PCC results for selecting datasets.
      */
     public boolean preferProviderOverPcc() {
@@ -1004,6 +1025,15 @@
         }
     }
 
+    /**
+     * Return if autofill should only fill in fields from current session.
+     */
+    public boolean getIsFillFieldsFromCurrentSessionOnly() {
+        synchronized (mFlagLock) {
+            return mIsFillFieldsFromCurrentSessionOnly;
+        }
+    }
+
     @Nullable
     @VisibleForTesting
     static Map<String, String[]> getAllowedCompatModePackages(String setting) {
@@ -2096,6 +2126,9 @@
                         pw.print(";");
                         pw.print("mPccProviderHints=");
                         pw.println(mPccProviderHints);
+                        pw.print(";");
+                        pw.print("mAutofillCredmanIntegrationEnabled=");
+                        pw.println(mAutofillCredmanIntegrationEnabled);
                     }
                     // Dump per-user services
                     dumpLocked("", pw);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 5b8bdd5..518b81f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.FillEventHistory.Event.NO_SAVE_UI_REASON_NONE;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
 import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED;
 import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
@@ -103,6 +104,10 @@
         extends AbstractPerUserSystemService<AutofillManagerServiceImpl, AutofillManagerService> {
 
     private static final String TAG = "AutofillManagerServiceImpl";
+
+    private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME =
+            new ComponentName("com.android.credentialmanager",
+                    "com.android.credentialmanager.autofill.CredentialAutofillService");
     private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
 
     /** Minimum interval to prune abandoned sessions */
@@ -532,9 +537,16 @@
 
         assertCallerLocked(clientActivity, compatMode);
 
-        // It's null when the session is just for augmented autofill
-        final ComponentName serviceComponentName = mInfo == null ? null
+        ComponentName serviceComponentName = mInfo == null ? null
                 : mInfo.getServiceInfo().getComponentName();
+
+        if (isAutofillCredmanIntegrationEnabled()
+                && ((flags & FLAG_SCREEN_HAS_CREDMAN_FIELD) != 0)) {
+            // Hardcode to credential manager proxy service
+            Slog.i(TAG, "Routing to CredentialAutofillService");
+            serviceComponentName = CREDMAN_SERVICE_COMPONENT_NAME;
+        }
+
         final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock,
                 sessionId, taskId, clientUid, clientActivityToken, clientCallback, hasCallback,
                 mUiLatencyHistory, mWtfHistory, serviceComponentName,
@@ -1747,6 +1759,10 @@
         }
     }
 
+    public boolean isAutofillCredmanIntegrationEnabled() {
+        return mMaster.isAutofillCredmanIntegrationEnabled();
+    }
+
     /**
      * Called when the {@link AutofillManagerService#mFieldClassificationResolver}
      * changed (among other places).
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
deleted file mode 100644
index 715697d..0000000
--- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill;
-
-import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-
-import static com.android.server.autofill.Helper.sVerbose;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.ICancellationSignal;
-import android.os.RemoteException;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.IFillCallback;
-import android.service.autofill.SaveInfo;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Maintains a client suggestions session with the
- * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
- *
- */
-final class ClientSuggestionsSession {
-
-    private static final String TAG = "ClientSuggestionsSession";
-    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
-
-    private final int mSessionId;
-    private final IAutoFillManagerClient mClient;
-    private final Handler mHandler;
-    private final ComponentName mComponentName;
-
-    private final RemoteFillService.FillServiceCallbacks mCallbacks;
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private AndroidFuture<FillResponse> mPendingFillRequest;
-    @GuardedBy("mLock")
-    private int mPendingFillRequestId = INVALID_REQUEST_ID;
-
-    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
-            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
-        mSessionId = sessionId;
-        mClient = client;
-        mHandler = handler;
-        mComponentName = componentName;
-        mCallbacks = callbacks;
-    }
-
-    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
-        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
-        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
-        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
-
-        mHandler.post(() -> {
-            if (sVerbose) {
-                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
-            }
-
-            try {
-                mClient.requestFillFromClient(requestId, inlineRequest,
-                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
-            } catch (RemoteException e) {
-                fillRequest.completeExceptionally(e);
-            }
-        });
-
-        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
-        futureRef.set(fillRequest);
-
-        synchronized (mLock) {
-            mPendingFillRequest = fillRequest;
-            mPendingFillRequestId = requestId;
-        }
-
-        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
-            synchronized (mLock) {
-                mPendingFillRequest = null;
-                mPendingFillRequestId = INVALID_REQUEST_ID;
-            }
-            if (err == null) {
-                processAutofillId(res);
-                mCallbacks.onFillRequestSuccess(requestId, res,
-                        mComponentName.getPackageName(), flags);
-            } else {
-                Slog.e(TAG, "Error calling on  client fill request", err);
-                if (err instanceof TimeoutException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                    mCallbacks.onFillRequestTimeout(requestId);
-                } else if (err instanceof CancellationException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                } else {
-                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
-                }
-            }
-        }));
-    }
-
-    /**
-     * Gets the application info for the component.
-     */
-    @Nullable
-    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
-        try {
-            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
-                    comp.getPackageName(),
-                    PackageManager.GET_META_DATA,
-                    userId);
-            if (si != null) {
-                return si;
-            }
-        } catch (RemoteException e) {
-        }
-        return null;
-    }
-
-    /**
-     * Gets the user-visible name of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadSafeLabel(
-                context.getPackageManager(), 0 /* do not ellipsize */,
-                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
-    }
-
-    /**
-     * Gets the user-visible icon of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
-    }
-
-    int cancelCurrentRequest() {
-        synchronized (mLock) {
-            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
-                    ? mPendingFillRequestId
-                    : INVALID_REQUEST_ID;
-        }
-    }
-
-    /**
-     * The {@link AutofillId} which the client gets from its view is not contain the session id,
-     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
-     * those ids in the Autofill framework, applies the current session id.
-     *
-     * @param res which response need to apply for a session id
-     */
-    private void processAutofillId(FillResponse res) {
-        if (res == null) {
-            return;
-        }
-
-        final List<Dataset> datasets = res.getDatasets();
-        if (datasets != null && !datasets.isEmpty()) {
-            for (int i = 0; i < datasets.size(); i++) {
-                final Dataset dataset = datasets.get(i);
-                if (dataset != null) {
-                    applySessionId(dataset.getFieldIds());
-                }
-            }
-        }
-
-        final SaveInfo saveInfo = res.getSaveInfo();
-        if (saveInfo != null) {
-            applySessionId(saveInfo.getOptionalIds());
-            applySessionId(saveInfo.getRequiredIds());
-            applySessionId(saveInfo.getSanitizerValues());
-            applySessionId(saveInfo.getTriggerId());
-        }
-    }
-
-    private void applySessionId(List<AutofillId> ids) {
-        if (ids == null || ids.isEmpty()) {
-            return;
-        }
-
-        for (int i = 0; i < ids.size(); i++) {
-            applySessionId(ids.get(i));
-        }
-    }
-
-    private void applySessionId(AutofillId[][] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId[] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId id) {
-        if (id == null) {
-            return;
-        }
-        id.setSessionId(mSessionId);
-    }
-
-    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
-        if (signal == null) {
-            return;
-        }
-        try {
-            signal.cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error requesting a cancellation", e);
-        }
-    }
-
-    private class FillCallbackImpl extends IFillCallback.Stub {
-        final AndroidFuture<FillResponse> mFillRequest;
-        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
-        final AtomicReference<ICancellationSignal> mCancellationSink;
-
-        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
-                AtomicReference<AndroidFuture<FillResponse>> futureRef,
-                AtomicReference<ICancellationSignal> cancellationSink) {
-            mFillRequest = fillRequest;
-            mFutureRef = futureRef;
-            mCancellationSink = cancellationSink;
-        }
-
-        @Override
-        public void onCancellable(ICancellationSignal cancellation) {
-            AndroidFuture<FillResponse> future = mFutureRef.get();
-            if (future != null && future.isCancelled()) {
-                dispatchCancellationSignal(cancellation);
-            } else {
-                mCancellationSink.set(cancellation);
-            }
-        }
-
-        @Override
-        public void onSuccess(FillResponse response) {
-            mFillRequest.complete(response);
-        }
-
-        @Override
-        public void onFailure(int requestId, CharSequence message) {
-            String errorMessage = message == null ? "" : String.valueOf(message);
-            mFillRequest.completeExceptionally(
-                    new RuntimeException(errorMessage));
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 423b85f..4688658 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -56,7 +56,7 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
-    private FillServiceCallbacks mCallbacks;
+    private final FillServiceCallbacks mCallbacks;
     private final Object mLock = new Object();
     private CompletableFuture<FillResponse> mPendingFillRequest;
     private int mPendingFillRequestId = INVALID_REQUEST_ID;
@@ -128,12 +128,9 @@
      */
     public int cancelCurrentRequest() {
         synchronized (mLock) {
-            int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false)
+            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
                     ? mPendingFillRequestId
                     : INVALID_REQUEST_ID;
-            mPendingFillRequest = null;
-            mPendingFillRequestId = INVALID_REQUEST_ID;
-            return canceledRequestId;
         }
     }
 
@@ -187,10 +184,6 @@
                 mPendingFillRequest = null;
                 mPendingFillRequestId = INVALID_REQUEST_ID;
             }
-            if (mCallbacks == null) {
-                Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
-                return;
-            }
             if (err == null) {
                 mCallbacks.onFillRequestSuccess(request.getId(), res,
                         mComponentName.getPackageName(), request.getFlags());
@@ -227,10 +220,6 @@
             return save;
         }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
                 .whenComplete((res, err) -> Handler.getMain().post(() -> {
-                    if (mCallbacks == null) {
-                        Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
-                        return;
-                    }
                     if (err == null) {
                         mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res);
                     } else {
@@ -245,8 +234,6 @@
     }
 
     public void destroy() {
-        cancelCurrentRequest();
         unbind();
-        mCallbacks = null;
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 265ed46..07e9c50 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,7 +16,6 @@
 
 package com.android.server.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
 import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
@@ -44,7 +43,6 @@
 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.FLAG_ENABLED_CLIENT_SUGGESTIONS;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -110,8 +108,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -481,9 +477,6 @@
      */
     private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
 
-    @Nullable
-    private ClientSuggestionsSession mClientSuggestionsSession;
-
     private final ClassificationState mClassificationState = new ClassificationState();
 
     // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
@@ -625,9 +618,6 @@
         /** Whether the current {@link FillResponse} is expired. */
         private boolean mExpiredResponse;
 
-        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
-        private boolean mClientSuggestionsEnabled;
-
         /** Whether the fill dialog UI is disabled. */
         private boolean mFillDialogDisabled;
 
@@ -673,19 +663,13 @@
                 }
                 mWaitForInlineRequest = inlineSuggestionsRequest != null;
                 mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                maybeRequestFillFromServiceLocked();
+                maybeRequestFillLocked();
                 viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
         }
 
-        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
-            mPendingFillRequest = null;
-            mWaitForInlineRequest = inlineRequest != null;
-            mPendingInlineSuggestionsRequest = inlineRequest;
-        }
-
         @GuardedBy("mLock")
-        void maybeRequestFillFromServiceLocked() {
+        void maybeRequestFillLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -696,15 +680,13 @@
                     return;
                 }
 
-                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
-                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                            mPendingFillRequest.getFillContexts(),
-                            mPendingFillRequest.getHints(),
-                            mPendingFillRequest.getClientState(),
-                            mPendingFillRequest.getFlags(),
-                            mPendingInlineSuggestionsRequest,
-                            mPendingFillRequest.getDelayedFillIntentSender());
-                }
+                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                        mPendingFillRequest.getFillContexts(),
+                        mPendingFillRequest.getHints(),
+                        mPendingFillRequest.getClientState(),
+                        mPendingFillRequest.getFlags(),
+                        mPendingInlineSuggestionsRequest,
+                        mPendingFillRequest.getDelayedFillIntentSender());
             }
             mLastFillRequest = mPendingFillRequest;
 
@@ -826,7 +808,7 @@
                             : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
-                maybeRequestFillFromServiceLocked();
+                maybeRequestFillLocked();
             }
 
             if (mActivityToken != null) {
@@ -1152,39 +1134,30 @@
     }
 
     /**
-     * Cancels the last request sent to the {@link #mRemoteFillService} or the
-     * {@link #mClientSuggestionsSession}.
+     * Cancels the last request sent to the {@link #mRemoteFillService}.
      */
     @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
-        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
-            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
-                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
-                    mSessionFlags.mAugmentedAutofillOnly);
+        if (mRemoteFillService == null) {
+            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
+                    + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
             return;
         }
+        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-        if (mRemoteFillService != null) {
-            final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
+        // Remove the FillContext as there will never be a response for the service
+        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+            final int numContexts = mContexts.size();
 
-            // Remove the FillContext as there will never be a response for the service
-            if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-                final int numContexts = mContexts.size();
-
-                // It is most likely the last context, hence search backwards
-                for (int i = numContexts - 1; i >= 0; i--) {
-                    if (mContexts.get(i).getRequestId() == canceledRequest) {
-                        if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
-                        mContexts.remove(i);
-                        break;
-                    }
+            // It is most likely the last context, hence search backwards
+            for (int i = numContexts - 1; i >= 0; i--) {
+                if (mContexts.get(i).getRequestId() == canceledRequest) {
+                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+                    mContexts.remove(i);
+                    break;
                 }
             }
         }
-
-        if (mClientSuggestionsSession != null) {
-            mClientSuggestionsSession.cancelCurrentRequest();
-        }
     }
 
     private boolean isViewFocusedLocked(int flags) {
@@ -1280,30 +1253,16 @@
             requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
         }
 
-        // Only ask IME to create inline suggestions request when
-        // 1. Autofill provider supports it or client enabled client suggestions.
-        // 2. The render service is available.
-        // 3. The view is focused. (The view may not be focused if the autofill is triggered
-        //    manually.)
+        // Only ask IME to create inline suggestions request if Autofill provider supports it and
+        // the render service is available except the autofill is triggered manually and the view
+        // is also not focused.
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
-                && remoteRenderService != null
-                && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) {
-            final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final int finalRequestId = requestId;
-                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
-                    // Using client suggestions
-                    synchronized (mLock) {
-                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
-                    }
-                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
-                };
-            } else {
-                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
-                        viewState, /* isInlineRequest= */ true);
-            }
+        if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null
+            && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
+            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+                mAssistReceiver.newAutofillRequestLocked(viewState,
+                    /* isInlineRequest= */ true);
             if (inlineSuggestionsRequestConsumer != null) {
                 final int requestIdCopy = requestId;
                 final AutofillId focusedId = mCurrentViewId;
@@ -1323,18 +1282,10 @@
                         inlineSuggestionRendorInfoCallback);
                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
-        } else if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Request client suggestions for the dropdown mode
-            onClientFillRequestLocked(requestId, null);
         } else {
             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
         }
 
-        if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Using client suggestions, unnecessary request AssistStructure
-            return;
-        }
-
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
     }
@@ -1443,11 +1394,6 @@
             mSessionFlags = new SessionFlags();
             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
-            if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                    == PackageManager.PERMISSION_GRANTED) {
-                mSessionFlags.mClientSuggestionsEnabled =
-                        (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
-            }
             setClientLocked(client);
         }
 
@@ -1599,15 +1545,14 @@
                 if (requestLog != null) {
                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                 }
-                processNullResponseOrFallbackLocked(requestId, requestFlags);
+                processNullResponseLocked(requestId, requestFlags);
                 return;
             }
 
             // TODO: Check if this is required. We can still present datasets to the user even if
             //  traditional field classification is disabled.
             fieldClassificationIds = response.getFieldClassificationIds();
-            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
-                    && !mService.isFieldClassificationEnabledLocked()) {
+            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                 processNullResponseLocked(requestId, requestFlags);
                 return;
@@ -1741,9 +1686,7 @@
                         || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
                             && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
                             && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
-                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())
-                        || (!mSessionFlags.mClientSuggestionsEnabled
-                        && !mService.isFieldClassificationEnabledLocked())));
+                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
         }
     }
 
@@ -2190,40 +2133,6 @@
         fieldFilters.add(dataset.getFilter(index));
     }
 
-    @GuardedBy("mLock")
-    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
-        if (!mSessionFlags.mClientSuggestionsEnabled) {
-            processNullResponseLocked(requestId, flags);
-            return;
-        }
-
-        // fallback to the default platform password manager
-        mSessionFlags.mClientSuggestionsEnabled = false;
-        mLastFillDialogTriggerIds = null;
-        // Log the existing FillResponse event.
-        mFillResponseEventLogger.logAndEndEvent();
-
-        final InlineSuggestionsRequest inlineRequest =
-                (mLastInlineSuggestionsRequest != null
-                        && mLastInlineSuggestionsRequest.first == requestId)
-                        ? mLastInlineSuggestionsRequest.second : null;
-
-        // Start a new FillRequest logger for client suggestion fallback.
-        mFillRequestEventLogger.startLogForNewRequest();
-        mRequestCount++;
-        mFillRequestEventLogger.maybeSetAppPackageUid(uid);
-        mFillRequestEventLogger.maybeSetFlags(
-            flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        mFillRequestEventLogger.maybeSetRequestTriggerReason(
-            TRIGGER_REASON_NORMAL_TRIGGER);
-        mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true);
-
-        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
-        requestAssistStructureLocked(requestId,
-                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        return;
-    }
-
     // FillServiceCallbacks
     @Override
     @SuppressWarnings("GuardedBy")
@@ -4520,22 +4429,13 @@
             filterText = value.getTextValue().toString();
         }
 
-        final CharSequence targetLabel;
-        final Drawable targetIcon;
-        synchronized (mLock) {
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
-                        mService.getUserId());
-                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
-                        mService.getMaster().getContext(), appInfo);
-                targetIcon = ClientSuggestionsSession.getAppIconLocked(
-                        mService.getMaster().getContext(), appInfo);
-            } else {
-                targetLabel = mService.getServiceLabelLocked();
-                targetIcon = mService.getServiceIconLocked();
-            }
+        final CharSequence serviceLabel;
+        final Drawable serviceIcon;
+        synchronized (this.mService.mLock) {
+            serviceLabel = mService.getServiceLabelLocked();
+            serviceIcon = mService.getServiceIconLocked();
         }
-        if (targetLabel == null || targetIcon == null) {
+        if (serviceLabel == null || serviceIcon == null) {
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
@@ -4596,7 +4496,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                targetLabel, targetIcon, this, mContext, id, mCompatMode,
+                serviceLabel, serviceIcon, this, mContext, id, mCompatMode,
                 mService.getMaster().getMaxInputLengthForAutofill());
 
         synchronized (mLock) {
@@ -4799,17 +4699,6 @@
             return false;
         }
 
-        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
-        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
-                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
-            if (sDebug) {
-                Slog.d(TAG, "Inline suggestions not supported for "
-                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
-                        + ". Falling back to dropdown.");
-            }
-            return false;
-        }
-
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -4824,7 +4713,7 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-                new InlineFillUi.InlineFillUiInfo(request, focusedId,
+                new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
                         filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
@@ -5641,26 +5530,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void onClientFillRequestLocked(int requestId,
-            InlineSuggestionsRequest inlineSuggestionsRequest) {
-        if (mClientSuggestionsSession == null) {
-            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
-                    mComponentName, this);
-        }
-
-        if (mContexts == null) {
-            mContexts = new ArrayList<>(1);
-        }
-        mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
-
-        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
-            inlineSuggestionsRequest = null;
-        }
-
-        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
-    }
-
     /**
      * The result of checking whether to show the save dialog, when session can be saved.
      *
@@ -6142,9 +6011,17 @@
                         continue;
                     }
                     final AutofillId viewId = dataset.getFieldIds().get(i);
+                    final ViewState viewState = mViewStates.get(viewId);
+                    if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly()
+                            && viewState != null && viewState.id.getSessionId() != id) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "Skipping filling view: " +
+                                    viewId + " as it isn't part of the current session: " + id);
+                        }
+                        continue;
+                    }
                     ids.add(viewId);
                     values.add(dataset.getFieldValues().get(i));
-                    final ViewState viewState = mViewStates.get(viewId);
                     if (viewState != null
                             && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) {
                         if (sVerbose) {
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 8a2aa61..4298c07 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -122,9 +122,9 @@
     private static final String TAG = "VirtualDeviceImpl";
 
     /**
-     * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent
-     * with virtual displays created via {@link DisplayManager} and allow for the creation of
-     * private, auto-mirror, and fixed orientation displays since
+     * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent
+     * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow
+     * for the creation of private, auto-mirror, and fixed orientation displays since
      * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
      *
      * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC
@@ -632,6 +632,10 @@
     public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
             @VirtualDeviceParams.DevicePolicy int devicePolicy) {
         super.setDevicePolicy_enforcePermission();
+        if (!Flags.dynamicPolicy()) {
+            return;
+        }
+
         switch (policyType) {
             case POLICY_TYPE_RECENTS:
                 synchronized (mVirtualDeviceLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index f594170..76ebdf4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -20,11 +20,15 @@
 import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
 import static android.service.contentcapture.ContentCaptureService.setClientState;
 import static android.view.contentcapture.ContentCaptureHelper.toList;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
 import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentprotection.flags.Flags.parseGroupsConfigEnabled;
 
 import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST;
 import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL;
@@ -99,9 +103,8 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
 import com.android.server.contentprotection.ContentProtectionConsentManager;
-import com.android.server.contentprotection.ContentProtectionPackageManager;
 import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -112,6 +115,8 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -138,6 +143,9 @@
     private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10;
     private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024;
 
+    private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP = ";";
+    private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE = ",";
+
     // Needed to pass checkstyle_hook as names are too long for one line.
     private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST =
             CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST;
@@ -198,10 +206,18 @@
     boolean mDevCfgEnableContentProtectionReceiver;
 
     @GuardedBy("mLock")
-    int mDevCfgContentProtectionAppsBlocklistSize;
+    int mDevCfgContentProtectionBufferSize;
 
     @GuardedBy("mLock")
-    int mDevCfgContentProtectionBufferSize;
+    @NonNull
+    List<List<String>> mDevCfgContentProtectionRequiredGroups;
+
+    @GuardedBy("mLock")
+    @NonNull
+    List<List<String>> mDevCfgContentProtectionOptionalGroups;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionOptionalGroupsThreshold;
 
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -217,7 +233,7 @@
 
     @Nullable private final ComponentName mContentProtectionServiceComponentName;
 
-    @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+    @Nullable private final ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
 
     @Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
 
@@ -226,6 +242,11 @@
                 com.android.internal.R.string.config_defaultContentCaptureService),
                 UserManager.DISALLOW_CONTENT_CAPTURE,
                 /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH);
+
+        mDevCfgContentProtectionRequiredGroups =
+                ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS;
+        mDevCfgContentProtectionOptionalGroups =
+                ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS;
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                 ActivityThread.currentApplication().getMainExecutor(),
                 (properties) -> onDeviceConfigChange(properties));
@@ -262,17 +283,15 @@
         if (getEnableContentProtectionReceiverLocked()) {
             mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
             if (mContentProtectionServiceComponentName != null) {
-                mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
-                mContentProtectionBlocklistManager.updateBlocklist(
-                        mDevCfgContentProtectionAppsBlocklistSize);
+                mContentProtectionAllowlistManager = createContentProtectionAllowlistManager();
                 mContentProtectionConsentManager = createContentProtectionConsentManager();
             } else {
-                mContentProtectionBlocklistManager = null;
+                mContentProtectionAllowlistManager = null;
                 mContentProtectionConsentManager = null;
             }
         } else {
             mContentProtectionServiceComponentName = null;
-            mContentProtectionBlocklistManager = null;
+            mContentProtectionAllowlistManager = null;
             mContentProtectionConsentManager = null;
         }
     }
@@ -420,8 +439,9 @@
                 case ContentCaptureManager
                         .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
-                case ContentCaptureManager
-                        .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -433,6 +453,8 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setFineTuneParamsFromDeviceConfig() {
+        String contentProtectionRequiredGroupsConfig;
+        String contentProtectionOptionalGroupsConfig;
         synchronized (mLock) {
             mDevCfgMaxBufferSize =
                     DeviceConfig.getInt(
@@ -472,20 +494,30 @@
                             ContentCaptureManager
                                     .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
                             ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
-            mDevCfgContentProtectionAppsBlocklistSize =
-                    DeviceConfig.getInt(
-                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                            ContentCaptureManager
-                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
-                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
-            // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
-            // it immutable at this point
             mDevCfgContentProtectionBufferSize =
                     DeviceConfig.getInt(
                             DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                             ContentCaptureManager
                                     .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE,
                             ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE);
+            contentProtectionRequiredGroupsConfig =
+                    DeviceConfig.getString(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG,
+                            ContentCaptureManager
+                                    .DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG);
+            contentProtectionOptionalGroupsConfig =
+                    DeviceConfig.getString(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG,
+                            ContentCaptureManager
+                                    .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG);
+            mDevCfgContentProtectionOptionalGroupsThreshold =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD,
+                            ContentCaptureManager
+                                    .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
             if (verbose) {
                 Slog.v(
                         TAG,
@@ -494,7 +526,7 @@
                                 + mDevCfgMaxBufferSize
                                 + ", idleFlush="
                                 + mDevCfgIdleFlushingFrequencyMs
-                                + ", textFluxh="
+                                + ", textFlush="
                                 + mDevCfgTextChangeFlushingFrequencyMs
                                 + ", logHistory="
                                 + mDevCfgLogHistorySize
@@ -504,12 +536,25 @@
                                 + mDevCfgDisableFlushForViewTreeAppearing
                                 + ", enableContentProtectionReceiver="
                                 + mDevCfgEnableContentProtectionReceiver
-                                + ", contentProtectionAppsBlocklistSize="
-                                + mDevCfgContentProtectionAppsBlocklistSize
                                 + ", contentProtectionBufferSize="
-                                + mDevCfgContentProtectionBufferSize);
+                                + mDevCfgContentProtectionBufferSize
+                                + ", contentProtectionRequiredGroupsConfig="
+                                + contentProtectionRequiredGroupsConfig
+                                + ", contentProtectionOptionalGroupsConfig="
+                                + contentProtectionOptionalGroupsConfig
+                                + ", contentProtectionOptionalGroupsThreshold="
+                                + mDevCfgContentProtectionOptionalGroupsThreshold);
             }
         }
+
+        List<List<String>> contentProtectionRequiredGroups =
+                parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig);
+        List<List<String>> contentProtectionOptionalGroups =
+                parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig);
+        synchronized (mLock) {
+            mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups;
+            mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups;
+        }
     }
 
     private void setLoggingLevelFromDeviceConfig() {
@@ -781,11 +826,17 @@
         pw.print("enableContentProtectionReceiver: ");
         pw.println(mDevCfgEnableContentProtectionReceiver);
         pw.print(prefix2);
-        pw.print("contentProtectionAppsBlocklistSize: ");
-        pw.println(mDevCfgContentProtectionAppsBlocklistSize);
-        pw.print(prefix2);
         pw.print("contentProtectionBufferSize: ");
         pw.println(mDevCfgContentProtectionBufferSize);
+        pw.print(prefix2);
+        pw.print("contentProtectionRequiredGroupsSize: ");
+        pw.println(mDevCfgContentProtectionRequiredGroups.size());
+        pw.print(prefix2);
+        pw.print("contentProtectionOptionalGroupsSize: ");
+        pw.println(mDevCfgContentProtectionOptionalGroups.size());
+        pw.print(prefix2);
+        pw.print("contentProtectionOptionalGroupsThreshold: ");
+        pw.println(mDevCfgContentProtectionOptionalGroupsThreshold);
         pw.print(prefix);
         pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
@@ -805,9 +856,8 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull
-    protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
-        return new ContentProtectionBlocklistManager(
-                new ContentProtectionPackageManager(getContext()));
+    protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+        return new ContentProtectionAllowlistManager();
     }
 
     /** @hide */
@@ -890,6 +940,42 @@
         return mContentCaptureManagerServiceStub;
     }
 
+    /**
+     * Parses a simple config in format "group;group" where each "group" is itself in the format of
+     * "string1,string2", eg:
+     *
+     * <p>"a" -> [["a"]]
+     *
+     * <p>"a,b" -> [["a", "b"]]
+     *
+     * <p>"a,b;c;d,e" -> [["a", "b"], ["c"], ["d", "e"]]
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) {
+        if (verbose) {
+            Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config);
+        }
+        if (!parseGroupsConfigEnabled()) {
+            return Collections.emptyList();
+        }
+        if (config == null) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(config.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP))
+                .map(this::parseContentProtectionGroupConfigValues)
+                .filter(group -> !group.isEmpty())
+                .toList();
+    }
+
+    private List<String> parseContentProtectionGroupConfigValues(@NonNull String group) {
+        return Arrays.stream(group.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE))
+                .filter(value -> !value.isEmpty())
+                .toList();
+    }
+
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
@@ -1277,7 +1363,10 @@
                                 isContentCaptureReceiverEnabled || whitelistedComponents != null,
                                 new ContentCaptureOptions.ContentProtectionOptions(
                                         isContentProtectionReceiverEnabled,
-                                        mDevCfgContentProtectionBufferSize),
+                                        mDevCfgContentProtectionBufferSize,
+                                        mDevCfgContentProtectionRequiredGroups,
+                                        mDevCfgContentProtectionOptionalGroups,
+                                        mDevCfgContentProtectionOptionalGroupsThreshold),
                                 whitelistedComponents);
                 if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
                 return options;
@@ -1318,7 +1407,7 @@
         private boolean isContentProtectionReceiverEnabled(
                 @UserIdInt int userId, @NonNull String packageName) {
             if (mContentProtectionServiceComponentName == null
-                    || mContentProtectionBlocklistManager == null
+                    || mContentProtectionAllowlistManager == null
                     || mContentProtectionConsentManager == null) {
                 return false;
             }
@@ -1326,9 +1415,13 @@
                 if (!mDevCfgEnableContentProtectionReceiver) {
                     return false;
                 }
+                if (mDevCfgContentProtectionRequiredGroups.isEmpty()
+                        && mDevCfgContentProtectionOptionalGroups.isEmpty()) {
+                    return false;
+                }
             }
             return mContentProtectionConsentManager.isConsentGranted(userId)
-                    && mContentProtectionBlocklistManager.isAllowed(packageName);
+                    && mContentProtectionAllowlistManager.isAllowed(packageName);
         }
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
new file mode 100644
index 0000000..59af526
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+/**
+ * Manages whether the content protection is enabled for an app using a allowlist.
+ *
+ * @hide
+ */
+public class ContentProtectionAllowlistManager {
+
+    private static final String TAG = "ContentProtectionAllowlistManager";
+
+    public ContentProtectionAllowlistManager() {}
+
+    /** Returns true if the package is allowed. */
+    public boolean isAllowed(@NonNull String packageName) {
+        Slog.v(TAG, packageName);
+        return false;
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
deleted file mode 100644
index a0fd28b..0000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.contentprotection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Manages whether the content protection is enabled for an app using a blocklist.
- *
- * @hide
- */
-public class ContentProtectionBlocklistManager {
-
-    private static final String TAG = "ContentProtectionBlocklistManager";
-
-    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
-            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
-    @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
-
-    @Nullable private Set<String> mPackageNameBlocklist;
-
-    public ContentProtectionBlocklistManager(
-            @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
-        mContentProtectionPackageManager = contentProtectionPackageManager;
-    }
-
-    public boolean isAllowed(@NonNull String packageName) {
-        if (mPackageNameBlocklist == null) {
-            // List not loaded or failed to load, don't run on anything
-            return false;
-        }
-        if (mPackageNameBlocklist.contains(packageName)) {
-            return false;
-        }
-        PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
-        if (packageInfo == null) {
-            return false;
-        }
-        if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
-            return false;
-        }
-        if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
-            return false;
-        }
-        if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
-            return false;
-        }
-        return true;
-    }
-
-    public void updateBlocklist(int blocklistSize) {
-        Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
-        mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
-    }
-
-    @Nullable
-    private Set<String> readPackageNameBlocklist(int blocklistSize) {
-        if (blocklistSize <= 0) {
-            // Explicitly requested an empty blocklist
-            return Collections.emptySet();
-        }
-        List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
-        if (lines == null) {
-            return null;
-        }
-        return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
-    }
-
-    @VisibleForTesting
-    @Nullable
-    protected List<String> readLinesFromRawFile(@NonNull String filename) {
-        try (FileReader fileReader = new FileReader(filename);
-                BufferedReader bufferedReader = new BufferedReader(fileReader)) {
-            return bufferedReader
-                    .lines()
-                    .map(line -> line.trim())
-                    .filter(line -> !line.isBlank())
-                    .collect(Collectors.toList());
-        } catch (Exception ex) {
-            Slog.e(TAG, "Failed to read: " + filename, ex);
-            return null;
-        }
-    }
-}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
deleted file mode 100644
index 4ebac07..0000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ /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.server.contentprotection;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.util.Slog;
-
-import java.util.Arrays;
-
-/**
- * Basic package manager for content protection using content capture.
- *
- * @hide
- */
-public class ContentProtectionPackageManager {
-
-    private static final String TAG = "ContentProtectionPackageManager";
-
-    private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
-            PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
-
-    @NonNull private final PackageManager mPackageManager;
-
-    public ContentProtectionPackageManager(@NonNull Context context) {
-        mPackageManager = context.getPackageManager();
-    }
-
-    @Nullable
-    public PackageInfo getPackageInfo(@NonNull String packageName) {
-        try {
-            return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
-        } catch (NameNotFoundException ex) {
-            Slog.w(TAG, "Package info not found for: " + packageName, ex);
-            return null;
-        }
-    }
-
-    public boolean isSystemApp(@NonNull PackageInfo packageInfo) {
-        return packageInfo.applicationInfo != null && isSystemApp(packageInfo.applicationInfo);
-    }
-
-    private boolean isSystemApp(@NonNull ApplicationInfo applicationInfo) {
-        return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-    }
-
-    public boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
-        return packageInfo.applicationInfo != null
-                && isUpdatedSystemApp(packageInfo.applicationInfo);
-    }
-
-    private boolean isUpdatedSystemApp(@NonNull ApplicationInfo applicationInfo) {
-        return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
-    }
-
-    public boolean hasRequestedInternetPermissions(@NonNull PackageInfo packageInfo) {
-        return packageInfo.requestedPermissions != null
-                && Arrays.asList(packageInfo.requestedPermissions)
-                        .contains(Manifest.permission.INTERNET);
-    }
-}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 6521fab..3985a0e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -96,11 +96,26 @@
     out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"],
 }
 
+/*
+ * This module is used to refer aconfig flag libraries that are
+ * added to the framework via static_libs.
+ * These libraries are referred here via libs prevent duplication of classes in both
+ * the framework and the system server.
+*/
+java_defaults {
+    name: "shared-framework-aconfig-libs",
+    libs: [
+        "display_flags_lib",
+        "camera_platform_flags_core_java_lib",
+    ],
+}
+
 java_library_static {
     name: "services.core.unboosted",
     defaults: [
         "platform_service_defaults",
         "android.hardware.power-java_static",
+        "shared-framework-aconfig-libs",
     ],
     srcs: [
         ":android.hardware.biometrics.face-V3-java-source",
@@ -133,12 +148,14 @@
         "android.hardware.light-V2.0-java",
         "android.hardware.gnss-V2-java",
         "android.hardware.vibrator-V2-java",
+        "android.nfc.flags-aconfig-java",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-art.stubs.system_server",
         "service-permission.stubs.system_server",
         "service-rkp.stubs.system_server",
         "service-sdksandbox.stubs.system_server",
+        "device_policy_aconfig_flags_lib",
     ],
     plugins: ["ImmutabilityAnnotationProcessor"],
 
@@ -181,18 +198,19 @@
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
         "cbor-java",
-        "display_flags_lib",
+        "dropbox_flags_lib",
         "icu4j_calendar_astronomer",
+        "android.security.aaid_aidl-java",
         "netd-client",
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
         "com.android.sysprop.watchdog",
         "ImmutabilityAnnotation",
         "securebox",
+        "android.content.pm.flags-aconfig-java",
         "apache-commons-math",
-        "power_optimization_flags_lib",
+        "backstage_power_flags_lib",
         "notification_flags_lib",
-        "camera_platform_flags_core_java_lib",
         "biometrics_flags_lib",
         "am_flags_lib",
     ],
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cd87908..638abdb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1387,11 +1387,12 @@
             @UserIdInt int userId);
 
     /**
-     * Tells PackageManager when a component (except BroadcastReceivers) of the package is used
+     * Tells PackageManager when a component of the package is used
      * and the package should get out of stopped state and be enabled.
      */
     public abstract void notifyComponentUsed(@NonNull String packageName,
-            @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo);
+            @UserIdInt int userId, @Nullable String recentCallingPackage,
+            @NonNull String debugInfo);
 
     /** @deprecated For legacy shell command only. */
     @Deprecated
@@ -1428,6 +1429,12 @@
             @UserIdInt int userId);
 
     /**
+     * Sends the PACKAGE_RESTARTED broadcast on the package manager handler thread.
+     */
+    public abstract void sendPackageRestartedBroadcast(@NonNull String packageName,
+            int uid, @Intent.Flags int flags);
+
+    /**
      * Return a list of all historical install sessions for the given user.
      */
     public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 556eba6..e9d4d76 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.os.Flags.stateOfHealthPublic;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
 
@@ -27,7 +28,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.health.HealthInfo;
 import android.hardware.health.V2_1.BatteryCapacityLevel;
@@ -1333,10 +1333,14 @@
         @Override
         public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
             switch (id) {
+                case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
+                    if (stateOfHealthPublic()) {
+                        break;
+                    }
+
                 case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
                 case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
                 case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
-                case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
                     mContext.enforceCallingPermission(
                             android.Manifest.permission.BATTERY_STATS, null);
                     break;
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 55069b7..f82a6aa 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -16,10 +16,14 @@
 
 package com.android.server;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +34,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Debug;
@@ -66,6 +71,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ObjectUtils;
 import com.android.server.DropBoxManagerInternal.EntrySource;
+import com.android.server.feature.flags.Flags;
 
 import libcore.io.IoUtils;
 
@@ -89,6 +95,13 @@
  * Clients use {@link DropBoxManager} to access this service.
  */
 public final class DropBoxManagerService extends SystemService {
+    /**
+     * For Android U and earlier versions, apps can continue to use the READ_LOGS permission,
+     * but for all subsequent versions, the READ_DROPBOX_DATA permission must be used.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long ENFORCE_READ_DROPBOX_DATA = 296060945L;
     private static final String TAG = "DropBoxManagerService";
     private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
     private static final int DEFAULT_MAX_FILES = 1000;
@@ -109,7 +122,6 @@
     // Tags that we should drop by default.
     private static final List<String> DISABLED_BY_DEFAULT_TAGS =
             List.of("data_app_wtf", "system_app_wtf", "system_server_wtf");
-
     // TODO: This implementation currently uses one file per entry, which is
     // inefficient for smallish entries -- consider using a single queue file
     // per tag (or even globally) instead.
@@ -291,8 +303,21 @@
             if (!DropBoxManagerService.this.mBooted) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
-            getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                    android.Manifest.permission.READ_LOGS, options);
+            if (Flags.enableReadDropboxPermission()) {
+                BroadcastOptions unbundledOptions = (options == null)
+                        ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
+
+                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                        Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+
+                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                        Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+            } else {
+                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                        android.Manifest.permission.READ_LOGS, options);
+            }
         }
 
         private Intent createIntent(String tag, long time) {
@@ -572,9 +597,16 @@
             return true;
         }
 
+
+        String permission = Manifest.permission.READ_LOGS;
+        if (Flags.enableReadDropboxPermission()
+                && CompatChanges.isChangeEnabled(ENFORCE_READ_DROPBOX_DATA, callingUid)) {
+            permission = Manifest.permission.READ_DROPBOX_DATA;
+        }
+
         // Callers always need this permission
-        getContext().enforceCallingOrSelfPermission(
-                android.Manifest.permission.READ_LOGS, TAG);
+        getContext().enforceCallingOrSelfPermission(permission, TAG);
+
 
         // Callers also need the ability to read usage statistics
         switch (getContext().getSystemService(AppOpsManager.class).noteOp(
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index d47a399..db89cac 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@
 # replaces 27510 with a row per notification
 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
 # a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
 # a notification was added to a autogroup
 27533 notification_autogrouped (key|3)
 # notification was removed from an autogroup
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 93dca2f..57ed5a2 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -41,6 +41,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -153,6 +154,9 @@
     @GuardedBy("this")
     private ArraySet<Integer> mPinKeys;
 
+    private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size";
+    private static final long DEFAULT_ANON_SIZE =
+            SystemProperties.getLong("pinner.pin_shared_anon_size", 0);
     private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB
     private long mPinAnonSize;
     private long mPinAnonAddress;
@@ -180,6 +184,17 @@
         }
     };
 
+    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace())
+                            && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) {
+                        refreshPinAnonConfig();
+                    }
+                }
+            };
+
     public PinnerService(Context context) {
         super(context);
 
@@ -206,6 +221,11 @@
 
         registerUidListener();
         registerUserSetupCompleteListener();
+
+        DeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                new HandlerExecutor(mPinnerHandler),
+                mDeviceConfigListener);
     }
 
     @Override
@@ -344,6 +364,8 @@
                 }
             }
         }
+
+        refreshPinAnonConfig();
     }
 
     /**
@@ -560,11 +582,6 @@
             pinKeys.add(KEY_ASSISTANT);
         }
 
-        mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
-                "pin_anon_size",
-                SystemProperties.getLong("pinner.pin_anon_size", 0));
-        mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE));
-
         return pinKeys;
     }
 
@@ -604,7 +621,6 @@
             int key = currentPinKeys.valueAt(i);
             pinApp(key, userHandle, true /* force */);
         }
-        pinAnonRegion();
     }
 
     /**
@@ -689,10 +705,28 @@
     }
 
     /**
+     * Handle any changes in the anon region pinner config.
+     */
+    private void refreshPinAnonConfig() {
+        long newPinAnonSize =
+                DeviceConfig.getLong(
+                        DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                        DEVICE_CONFIG_KEY_ANON_SIZE,
+                        DEFAULT_ANON_SIZE);
+        newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
+        if (newPinAnonSize != mPinAnonSize) {
+            mPinAnonSize = newPinAnonSize;
+            pinAnonRegion();
+        }
+    }
+
+    /**
      * Pin an empty anonymous region. This should only be used for ablation experiments.
      */
     private void pinAnonRegion() {
         if (mPinAnonSize == 0) {
+            Slog.d(TAG, "pinAnonRegion: releasing pinned region");
+            unpinAnonRegion();
             return;
         }
         long alignedPinSize = mPinAnonSize;
@@ -700,15 +734,21 @@
             alignedPinSize -= alignedPinSize % PAGE_SIZE;
             Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize);
         }
-        if (mPinAnonAddress != 0
-                && mCurrentlyPinnedAnonSize != alignedPinSize) {
+        if (mPinAnonAddress != 0) {
+            if (mCurrentlyPinnedAnonSize == alignedPinSize) {
+                Slog.d(TAG, "pinAnonRegion: already pinned region of size " + alignedPinSize);
+                return;
+            }
+            Slog.d(TAG, "pinAnonRegion: resetting pinned region for new size " + alignedPinSize);
             unpinAnonRegion();
         }
         long address = 0;
         try {
+            // Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status).
+            // The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo.
             address = Os.mmap(0, alignedPinSize,
                     OsConstants.PROT_READ | OsConstants.PROT_WRITE,
-                    OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS,
+                    OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS,
                     new FileDescriptor(), /*offset=*/0);
 
             Unsafe tempUnsafe = null;
@@ -729,7 +769,7 @@
             mCurrentlyPinnedAnonSize = alignedPinSize;
             mPinAnonAddress = address;
             address = -1;
-            Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
+            Slog.w(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
         } catch (Exception ex) {
             Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex);
             return;
@@ -744,6 +784,8 @@
         if (mPinAnonAddress != 0) {
             safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
         }
+        mPinAnonAddress = 0;
+        mCurrentlyPinnedAnonSize = 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 962f38f..15fc2dc 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -214,9 +214,12 @@
     // external storage service.
     public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
 
-     /** Extended timeout for the system server watchdog. */
+    /** Extended timeout for the system server watchdog. */
     private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
 
+    /** Extended timeout for the system server watchdog for vold#partition operation. */
+    private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
+
     @GuardedBy("mLock")
     private final Set<Integer> mFuseMountedUser = new ArraySet<>();
 
@@ -383,15 +386,14 @@
     private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE);
 
     /**
-     * mLocalUnlockedUsers affects the return value of isUserUnlocked.  If
-     * any value in the array changes, then the binder cache for
-     * isUserUnlocked must be invalidated.  When adding mutating methods to
-     * WatchedLockedUsers, be sure to invalidate the cache in the new
-     * methods.
+     * mCeUnlockedUsers affects the return value of {@link UserManager#isUserUnlocked}.  If any
+     * value in the array changes, then the binder cache for {@link UserManager#isUserUnlocked} must
+     * be invalidated.  When adding mutating methods to this class, be sure to invalidate the cache
+     * in the new methods.
      */
-    private static class WatchedLockedUsers {
+    private static class WatchedUnlockedUsers {
         private int[] users = EmptyArray.INT;
-        public WatchedLockedUsers() {
+        public WatchedUnlockedUsers() {
             invalidateIsUserUnlockedCache();
         }
         public void append(int userId) {
@@ -423,10 +425,14 @@
         }
     }
 
-    /** Set of users that we know are unlocked. */
+    /** Set of users whose CE storage is unlocked. */
     @GuardedBy("mLock")
-    private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
-    /** Set of users that system knows are unlocked. */
+    private WatchedUnlockedUsers mCeUnlockedUsers = new WatchedUnlockedUsers();
+
+    /**
+     * Set of users that are in the RUNNING_UNLOCKED state.  This differs from {@link
+     * mCeUnlockedUsers} in that a user can be stopped but still have its CE storage unlocked.
+     */
     @GuardedBy("mLock")
     private int[] mSystemUnlockedUsers = EmptyArray.INT;
 
@@ -1141,11 +1147,10 @@
         }
     }
 
-    // If vold knows that some users have their storage unlocked already (which
-    // can happen after a "userspace reboot"), then add those users to
-    // mLocalUnlockedUsers.  Do this right away and don't wait until
-    // PHASE_BOOT_COMPLETED, since the system may unlock users before then.
-    private void restoreLocalUnlockedUsers() {
+    // If vold knows that some users have their CE storage unlocked already (which can happen after
+    // a "userspace reboot"), then add those users to mCeUnlockedUsers.  Do this right away and
+    // don't wait until PHASE_BOOT_COMPLETED, since the system may unlock users before then.
+    private void restoreCeUnlockedUsers() {
         final int[] userIds;
         try {
             userIds = mVold.getUnlockedUsers();
@@ -1161,7 +1166,7 @@
                 // reconnecting to vold after it crashed and was restarted, in
                 // which case things will be the other way around --- we'll know
                 // about the unlocked users but vold won't.
-                mLocalUnlockedUsers.appendAll(userIds);
+                mCeUnlockedUsers.appendAll(userIds);
             }
         }
     }
@@ -1233,7 +1238,7 @@
     private void onUserStopped(int userId) {
         Slog.d(TAG, "onUserStopped " + userId);
 
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
         try {
             mVold.onUserStopped(userId);
@@ -1317,7 +1322,7 @@
                 unlockedUsers.add(userId);
             }
         }
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
         for (Integer userId : unlockedUsers) {
             try {
@@ -2072,7 +2077,7 @@
                 connectVold();
             }, DateUtils.SECOND_IN_MILLIS);
         } else {
-            restoreLocalUnlockedUsers();
+            restoreCeUnlockedUsers();
             onDaemonConnected();
         }
     }
@@ -2338,6 +2343,8 @@
         try {
             // TODO(b/135341433): Remove cautious logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
+                    SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                 @Override
                 public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2463,10 +2470,12 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionPublic(String diskId) {
-
         super.partitionPublic_enforcePermission();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2483,6 +2492,9 @@
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2499,6 +2511,9 @@
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2992,7 +3007,7 @@
             // We need all the users unlocked to move their primary storage
             users = mContext.getSystemService(UserManager.class).getUsers();
             for (UserInfo user : users) {
-                if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) {
+                if (StorageManager.isFileEncrypted() && !isCeStorageUnlocked(user.id)) {
                     Slog.w(TAG, "Failing move due to locked user " + user.id);
                     onMoveStatusLocked(PackageManager.MOVE_FAILED_LOCKED_USER);
                     return;
@@ -3216,15 +3231,15 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
 
-        super.createUserKey_enforcePermission();
+        super.createUserStorageKeys_enforcePermission();
 
         try {
-            mVold.createUserKey(userId, serialNumber, ephemeral);
-            // New keys are always unlocked.
+            mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
+            // Since the user's CE key was just created, the user's CE storage is now unlocked.
             synchronized (mLock) {
-                mLocalUnlockedUsers.append(userId);
+                mCeUnlockedUsers.append(userId);
             }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -3233,15 +3248,15 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void destroyUserKey(int userId) {
+    public void destroyUserStorageKeys(int userId) {
 
-        super.destroyUserKey_enforcePermission();
+        super.destroyUserStorageKeys_enforcePermission();
 
         try {
-            mVold.destroyUserKey(userId);
-            // Destroying a key also locks it.
+            mVold.destroyUserStorageKeys(userId);
+            // Since the user's CE key was just destroyed, the user's CE storage is now locked.
             synchronized (mLock) {
-                mLocalUnlockedUsers.remove(userId);
+                mCeUnlockedUsers.remove(userId);
             }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -3251,60 +3266,60 @@
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
-        super.setUserKeyProtection_enforcePermission();
+    public void setCeStorageProtection(@UserIdInt int userId, byte[] secret)
+            throws RemoteException {
+        super.setCeStorageProtection_enforcePermission();
 
-        mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
+        mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
     }
 
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
-        throws RemoteException {
-        super.unlockUserKey_enforcePermission();
+    public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret)
+            throws RemoteException {
+        super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
-            mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
+            mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret));
         }
         synchronized (mLock) {
-            mLocalUnlockedUsers.append(userId);
+            mCeUnlockedUsers.append(userId);
         }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void lockUserKey(int userId) {
-        //  Do not lock user 0 data for headless system user
-        super.lockUserKey_enforcePermission();
+    public void lockCeStorage(int userId) {
+        super.lockCeStorage_enforcePermission();
 
+        // Never lock the CE storage of a headless system user.
         if (userId == UserHandle.USER_SYSTEM
                 && UserManager.isHeadlessSystemUserMode()) {
             throw new IllegalArgumentException("Headless system user data cannot be locked..");
         }
 
-
-        if (!isUserKeyUnlocked(userId)) {
+        if (!isCeStorageUnlocked(userId)) {
             Slog.d(TAG, "User " + userId + "'s CE storage is already locked");
             return;
         }
 
         try {
-            mVold.lockUserKey(userId);
+            mVold.lockCeStorage(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return;
         }
 
         synchronized (mLock) {
-            mLocalUnlockedUsers.remove(userId);
+            mCeUnlockedUsers.remove(userId);
         }
     }
 
     @Override
-    public boolean isUserKeyUnlocked(int userId) {
+    public boolean isCeStorageUnlocked(int userId) {
         synchronized (mLock) {
-            return mLocalUnlockedUsers.contains(userId);
+            return mCeUnlockedUsers.contains(userId);
         }
     }
 
@@ -3607,7 +3622,7 @@
 
         @Override
         public ParcelFileDescriptor open() throws AppFuseMountException {
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow");
             try {
                 final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
@@ -3621,7 +3636,7 @@
         @Override
         public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
                 throws AppFuseMountException {
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow");
             try {
                 return new ParcelFileDescriptor(
@@ -3633,7 +3648,7 @@
 
         @Override
         public void close() throws Exception {
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
             if (mMounted) {
                 mVold.unmountAppFuse(uid, mountId);
@@ -3704,8 +3719,8 @@
         final int userId = UserHandle.getUserId(callingUid);
         final String propertyName = "sys.user." + userId + ".ce_available";
 
-        // Ignore requests to create directories while storage is locked
-        if (!isUserKeyUnlocked(userId)) {
+        // Ignore requests to create directories while CE storage is locked
+        if (!isCeStorageUnlocked(userId)) {
             throw new IllegalStateException("Failed to prepare " + appPath);
         }
 
@@ -3831,15 +3846,15 @@
         final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
 
         final boolean userIsDemo;
-        final boolean userKeyUnlocked;
         final boolean storagePermission;
+        final boolean ceStorageUnlocked;
         final long token = Binder.clearCallingIdentity();
         try {
             userIsDemo = LocalServices.getService(UserManagerInternal.class)
                     .getUserInfo(userId).isDemo();
             storagePermission = mStorageManagerInternal.hasExternalStorage(callingUid,
                     callingPackage);
-            userKeyUnlocked = isUserKeyUnlocked(userId);
+            ceStorageUnlocked = isCeStorageUnlocked(userId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3899,7 +3914,7 @@
                 } else if (!systemUserUnlocked) {
                     reportUnmounted = true;
                     Slog.w(TAG, "Reporting " + volId + " unmounted due to system locked");
-                } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !userKeyUnlocked) {
+                } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !ceStorageUnlocked) {
                     reportUnmounted = true;
                     Slog.w(TAG, "Reporting " + volId + "unmounted due to " + userId + " locked");
                 } else if (!storagePermission && !realState) {
@@ -4703,7 +4718,7 @@
             }
 
             pw.println();
-            pw.println("Local unlocked users: " + mLocalUnlockedUsers);
+            pw.println("CE unlocked users: " + mCeUnlockedUsers);
             pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
         }
 
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 55aa716..003046a 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,7 +250,7 @@
         private Monitor mCurrentMonitor;
         private long mStartTimeMillis;
         private int mPauseCount;
-        private long mOneOffTimeoutMillis;
+        private long mPauseEndTimeMillis;
 
         HandlerChecker(Handler handler, String name) {
             mHandler = handler;
@@ -270,20 +270,19 @@
          * @param handlerCheckerTimeoutMillis the timeout to use for this run
          */
         public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
-            if (mOneOffTimeoutMillis > 0) {
-              mWaitMaxMillis = mOneOffTimeoutMillis;
-              mOneOffTimeoutMillis = 0;
-            } else {
-              mWaitMaxMillis = handlerCheckerTimeoutMillis;
-            }
+            mWaitMaxMillis = handlerCheckerTimeoutMillis;
 
             if (mCompleted) {
                 // Safe to update monitors in queue, Handler is not in the middle of work
                 mMonitors.addAll(mMonitorQueue);
                 mMonitorQueue.clear();
             }
+
+            long nowMillis = SystemClock.uptimeMillis();
+            boolean isPaused = mPauseCount > 0
+                    || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis);
             if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
-                    || (mPauseCount > 0)) {
+                    || isPaused) {
                 // Don't schedule until after resume OR
                 // If the target looper has recently been polling, then
                 // there is no reason to enqueue our checker on it since that
@@ -301,7 +300,8 @@
 
             mCompleted = false;
             mCurrentMonitor = null;
-            mStartTimeMillis = SystemClock.uptimeMillis();
+            mStartTimeMillis = nowMillis;
+            mPauseEndTimeMillis = 0;
             mHandler.postAtFrontOfQueue(this);
         }
 
@@ -360,20 +360,19 @@
         }
 
         /**
-         * Sets the timeout of the HandlerChecker for one run.
+         * Pauses the checks for the given time.
          *
-         * <p>The current run will be ignored and the next run will be set to this timeout.
-         *
-         * <p>If a one off timeout is already set, the maximum timeout will be used.
+         * <p>The current run will be ignored and another run will be scheduled after
+         * the given time.
          */
-        public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) {
-            mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis);
+        public void pauseForLocked(int pauseMillis, String reason) {
+            mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis;
             // Mark as completed, because there's a chance we called this after the watchog
             // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
             // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
             mCompleted = true;
-            Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: "
-                    + reason + ". New timeout: " + mOneOffTimeoutMillis);
+            Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: "
+                    + reason + ". Pause end time: " + mPauseEndTimeMillis);
         }
 
         /** Pause the HandlerChecker. */
@@ -623,34 +622,32 @@
     }
 
      /**
-     * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful
+     * Pauses the checks of the watchdog for this thread. This is useful
      * to run a slow operation on one of the monitored thread.
      *
-     * <p>After the next run, the timeout will go back to the default value.
-     *
-     * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
-     *
-     * <p>If a one-off timeout for the current thread is already, the max value will be used.
+     * <p>After the given time, the timeout will go back to the default value.
+     * <p>This method does not require resume to be called.
      */
-    public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) {
+    public void pauseWatchingCurrentThreadFor(int pauseMillis, String reason) {
         synchronized (mLock) {
             for (HandlerCheckerAndTimeout hc : mHandlerCheckers) {
                 HandlerChecker checker = hc.checker();
                 if (Thread.currentThread().equals(checker.getThread())) {
-                    checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+                    checker.pauseForLocked(pauseMillis, reason);
                 }
             }
         }
     }
 
     /**
-     * Sets a one-off timeout for the next run of the watchdog for the monitor thread.
+     * Pauses the checks of the watchdog for the monitor thread for the given time
      *
-     * <p>Simiar to {@link setOneOffTimeoutForCurrentThread} but used for monitors added through
-     * {@link #addMonitor}
+     * <p>Similar to {@link pauseWatchingCurrentThreadFor} but used for monitors added
+     * through {@link #addMonitor}
+     * <p>This method does not require resume to be called.
      */
-    public void setOneOffTimeoutForMonitors(int oneOffTimeoutMillis, String reason) {
-        mMonitorChecker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+    public void pauseWatchingMonitorsFor(int pauseMillis, String reason) {
+        mMonitorChecker.pauseForLocked(pauseMillis, reason);
     }
 
     /**
@@ -664,7 +661,7 @@
      * adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
      *
      * <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
-     * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread.
+     * pauses have been resumed. Prefer to use #pauseWatchingCurrentThreadFor.
      */
     public void pauseWatchingCurrentThread(String reason) {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 5fb889a..bdda95e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -190,6 +190,7 @@
 
     final MessageHandler mHandler;
 
+    private static final int TIMEOUT_DELAY_MS = 1000 * 60;
     // Messages that can be sent on mHandler
     private static final int MESSAGE_TIMED_OUT = 3;
     private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
@@ -4942,6 +4943,7 @@
             synchronized (mSessions) {
                 mSessions.put(toString(), this);
             }
+            scheduleTimeout();
             if (response != null) {
                 try {
                     response.asBinder().linkToDeath(this, 0 /* flags */);
@@ -5109,6 +5111,11 @@
             }
         }
 
+        private void scheduleTimeout() {
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
+        }
+
         public void cancelTimeout() {
             mHandler.removeMessages(MESSAGE_TIMED_OUT, this);
         }
@@ -5146,6 +5153,9 @@
 
         public void onTimedOut() {
             IAccountManagerResponse response = getResponseAndClose();
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Session.onTimedOut");
+            }
             if (response != null) {
                 try {
                     response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
@@ -5309,7 +5319,7 @@
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
             }
-            long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE;
+            long flags = Context.BIND_AUTO_CREATE;
             if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) {
                 flags |= Context.BIND_ALLOW_INSTANT;
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 553b085..5f1a7e7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -409,6 +409,13 @@
 
     AppWidgetManagerInternal mAppWidgetManagerInternal;
 
+    /**
+     * The available ANR timers.
+     */
+    private final ProcessAnrTimer mActiveServiceAnrTimer;
+    private final ServiceAnrTimer mShortFGSAnrTimer;
+    private final ServiceAnrTimer mServiceFGAnrTimer;
+
     // allowlisted packageName.
     ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
 
@@ -663,6 +670,15 @@
 
         final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
         this.mFGSLogger = new ForegroundServiceTypeLoggerModule();
+        this.mActiveServiceAnrTimer = new ProcessAnrTimer(service,
+                ActivityManagerService.SERVICE_TIMEOUT_MSG,
+                "SERVICE_TIMEOUT");
+        this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
+                ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
+                "FGS_TIMEOUT");
+        this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
+                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
+                "SERVICE_FOREGROUND_TIMEOUT");
     }
 
     void systemServicesReady() {
@@ -2083,8 +2099,7 @@
                 r.fgRequired = false;
                 r.fgWaiting = false;
                 alreadyStartedOp = stopProcStatsOp = true;
-                mAm.mHandler.removeMessages(
-                        ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+                mServiceFGAnrTimer.cancel(r);
             }
 
             final ProcessServiceRecord psr = r.app.mServices;
@@ -3313,7 +3328,7 @@
     }
 
     void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
-        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+        mShortFGSAnrTimer.cancel(sr);
         mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG,
                 sr);
         mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
@@ -3387,9 +3402,11 @@
                     Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr
                             + " " + sr.getShortFgsTimedEventDescription(nowUptime));
                 }
+                mShortFGSAnrTimer.discard(sr);
                 return;
             }
             Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+            mShortFGSAnrTimer.accept(sr);
             traceInstant("short FGS timeout: ", sr);
 
             logFGSStateChangeLocked(sr,
@@ -3413,11 +3430,10 @@
                         msg, sr.getShortFgsInfo().getProcStateDemoteTime());
             }
 
-            {
-                final Message msg = mAm.mHandler.obtainMessage(
-                        ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
-                mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
-            }
+            // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now".
+            // Compute the time from "now" when starting the anr timer.
+            mShortFGSAnrTimer.start(sr,
+                    sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis());
         }
     }
 
@@ -3662,8 +3678,8 @@
                 || (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 filterOutQuarantined =
-                (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0;
+        final boolean matchQuarantined =
+                (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;
 
         ProcessRecord attributedApp = null;
         if (sdkSandboxClientAppUid > 0) {
@@ -3673,7 +3689,7 @@
                 isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
                 resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
                 isBindExternal, allowInstant, null /* fgsDelegateOptions */,
-                inSharedIsolatedProcess, filterOutQuarantined);
+                inSharedIsolatedProcess, matchQuarantined);
         if (res == null) {
             return 0;
         }
@@ -4186,7 +4202,7 @@
                 sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
                 callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
                 allowInstant, fgsDelegateOptions, inSharedIsolatedProcess,
-                false /* filterOutQuarantined */);
+                false /* matchQuarantined */);
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -4195,7 +4211,7 @@
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
-            boolean inSharedIsolatedProcess, boolean filterOutQuarantined) {
+            boolean inSharedIsolatedProcess, boolean matchQuarantined) {
         if (isSdkSandboxService && instanceName == null) {
             throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
         }
@@ -4317,8 +4333,8 @@
                 if (allowInstant) {
                     flags |= PackageManager.MATCH_INSTANT;
                 }
-                if (filterOutQuarantined) {
-                    flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
+                if (matchQuarantined) {
+                    flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
                 }
                 // TODO: come back and remove this assumption to triage all services
                 ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service,
@@ -4847,8 +4863,7 @@
         // a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT
         // again.
         if (r.fgRequired && r.fgWaiting) {
-            mAm.mHandler.removeMessages(
-                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+            mServiceFGAnrTimer.cancel(r);
             r.fgWaiting = false;
         }
 
@@ -5691,8 +5706,7 @@
             }
             mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                     AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
-            mAm.mHandler.removeMessages(
-                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+            mServiceFGAnrTimer.cancel(r);
             if (r.app != null) {
                 Message msg = mAm.mHandler.obtainMessage(
                         ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
@@ -6128,7 +6142,7 @@
                 if (psr.numberOfExecutingServices() == 0) {
                     if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
                             "No more executingServices of " + r.shortInstanceName);
-                    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
+                    if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app);
                 } else if (r.executeFg) {
                     // Need to re-evaluate whether the app still needs to be in the foreground.
                     for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6816,13 +6830,16 @@
             synchronized (mAm) {
                 if (proc.isDebugging()) {
                     // The app's being debugged, ignore timeout.
+                    mActiveServiceAnrTimer.discard(proc);
                     return;
                 }
                 final ProcessServiceRecord psr = proc.mServices;
                 if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
                         || proc.isKilled()) {
+                    mActiveServiceAnrTimer.discard(proc);
                     return;
                 }
+                mActiveServiceAnrTimer.accept(proc);
                 final long now = SystemClock.uptimeMillis();
                 final long maxTime =  now
                         - (psr.shouldExecServicesFg()
@@ -6855,12 +6872,11 @@
                     timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName,
                             waitedMillis);
                 } else {
-                    Message msg = mAm.mHandler.obtainMessage(
-                            ActivityManagerService.SERVICE_TIMEOUT_MSG);
-                    msg.obj = proc;
-                    mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
-                            ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
-                            (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
+                    final long delay = psr.shouldExecServicesFg()
+                                       ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+                                       (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)
+                                       - SystemClock.uptimeMillis();
+                    mActiveServiceAnrTimer.start(proc, delay);
                 }
             }
 
@@ -6886,12 +6902,15 @@
             synchronized (mAm) {
                 timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
                 if (!r.fgRequired || !r.fgWaiting || r.destroying) {
+                    mServiceFGAnrTimer.discard(r);
                     return;
                 }
 
+                mServiceFGAnrTimer.accept(r);
                 app = r.app;
                 if (app != null && app.isDebugging()) {
                     // The app's being debugged; let it ride
+                    mServiceFGAnrTimer.discard(r);
                     return;
                 }
 
@@ -6948,26 +6967,46 @@
                 ForegroundServiceDidNotStartInTimeException.createExtrasForService(service));
     }
 
+    private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> {
+
+        ProcessAnrTimer(ActivityManagerService am, int msg, String label) {
+            super(Objects.requireNonNull(am).mHandler, msg, label);
+        }
+
+        void start(@NonNull ProcessRecord proc, long millis) {
+            start(proc, proc.getPid(), proc.uid, millis);
+        }
+    }
+
+    private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> {
+
+        ServiceAnrTimer(ActivityManagerService am, int msg, String label) {
+            super(Objects.requireNonNull(am).mHandler, msg, label);
+        }
+
+        void start(@NonNull ServiceRecord service, long millis) {
+            start(service,
+                    (service.app != null) ? service.app.getPid() : 0,
+                    service.appInfo.uid,
+                    millis);
+        }
+    }
+
     void scheduleServiceTimeoutLocked(ProcessRecord proc) {
         if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
             return;
         }
-        Message msg = mAm.mHandler.obtainMessage(
-                ActivityManagerService.SERVICE_TIMEOUT_MSG);
-        msg.obj = proc;
-        mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
-                ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
+        final long delay = proc.mServices.shouldExecServicesFg()
+                ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT;
+        mActiveServiceAnrTimer.start(proc, delay);
     }
 
     void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
         if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
             return;
         }
-        Message msg = mAm.mHandler.obtainMessage(
-                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
-        msg.obj = r;
         r.fgWaiting = true;
-        mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
+        mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs);
     }
 
     final class ServiceDumper {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d3ce47c..e88d0c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -58,7 +58,6 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
 import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
 import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
@@ -78,8 +77,10 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_BOOT_COMPLETED;
 import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
 import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -377,6 +378,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -562,7 +564,7 @@
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
 
     // How long we wait for a launched process to complete its app startup before we ANR.
-    static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+    static final int BIND_APPLICATION_TIMEOUT = 15 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
 
     // How long we wait to kill an application zygote, after the last process using
     // it has gone away.
@@ -1531,6 +1533,11 @@
      */
     int mBootPhase;
 
+    /**
+     * The time stamp that all apps have received BOOT_COMPLETED.
+     */
+    volatile long mBootCompletedTimestamp;
+
     @GuardedBy("this")
     boolean mDeterministicUidIdle = false;
 
@@ -1630,7 +1637,8 @@
     static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
     static final int ADD_UID_TO_OBSERVER_MSG = 80;
     static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
-    static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
+    static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
+    static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1983,15 +1991,11 @@
                 case UPDATE_CACHED_APP_HIGH_WATERMARK: {
                     mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
                 } break;
-                case BIND_APPLICATION_TIMEOUT_MSG: {
-                    ProcessRecord app = (ProcessRecord) msg.obj;
-
-                    final String anrMessage;
-                    synchronized (app) {
-                        anrMessage = "Process " + app + " failed to complete startup";
-                    }
-
-                    mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+                case BIND_APPLICATION_TIMEOUT_SOFT_MSG: {
+                    handleBindApplicationTimeoutSoft((ProcessRecord) msg.obj, msg.arg1);
+                } break;
+                case BIND_APPLICATION_TIMEOUT_HARD_MSG: {
+                    handleBindApplicationTimeoutHard((ProcessRecord) msg.obj);
                 } break;
             }
         }
@@ -2295,7 +2299,7 @@
             return;
         }
         // TODO(b/148767783): should we check all profiles under user0?
-        UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+        UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId),
                 BackgroundThread.getExecutor());
     }
 
@@ -4160,26 +4164,34 @@
 
     @GuardedBy("this")
     private void finishForceStopPackageLocked(final String packageName, int uid) {
-        Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
-                Uri.fromParts("package", packageName, null));
+        int flags = 0;
         if (!mProcessesReady) {
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                    | Intent.FLAG_RECEIVER_FOREGROUND);
+            flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                    | Intent.FLAG_RECEIVER_FOREGROUND;
         }
-        final int userId = UserHandle.getUserId(uid);
-        final int[] broadcastAllowList =
-                getPackageManagerInternal().getVisibilityAllowList(packageName, userId);
-        intent.putExtra(Intent.EXTRA_UID, uid);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        broadcastIntentLocked(null /* callerApp */, null /* callerPackage */,
-                null /* callerFeatureId */, intent, null /* resolvedType */,
-                null /* resultToApp */, null /* resultTo */,
-                0 /* resultCode */, null /* resultData */, null /* resultExtras */,
-                null /* requiredPermissions */, null /* excludedPermissions */,
-                null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
-                false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
-                Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE,
-                broadcastAllowList, null /* filterExtrasForReceiver */);
+        if (android.content.pm.Flags.stayStopped()) {
+            // Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED
+            mPackageManagerInt.sendPackageRestartedBroadcast(packageName,
+                    uid, flags);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
+                    Uri.fromParts("package", packageName, null));
+            intent.addFlags(flags);
+            final int userId = UserHandle.getUserId(uid);
+            final int[] broadcastAllowList =
+                    getPackageManagerInternal().getVisibilityAllowList(packageName, userId);
+            intent.putExtra(Intent.EXTRA_UID, uid);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            broadcastIntentLocked(null /* callerApp */, null /* callerPackage */,
+                    null /* callerFeatureId */, intent, null /* resolvedType */,
+                    null /* resultToApp */, null /* resultTo */,
+                    0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+                    null /* requiredPermissions */, null /* excludedPermissions */,
+                    null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
+                    false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                    Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE,
+                    broadcastAllowList, null /* filterExtrasForReceiver */);
+        }
     }
 
     private void cleanupDisabledPackageComponentsLocked(
@@ -4636,7 +4648,7 @@
             // We carefully use the same state that PackageManager uses for
             // filtering, since we use this flag to decide if we need to install
             // providers when user is unlocked later
-            app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
+            app.setUnlocked(StorageManager.isCeStorageUnlocked(app.userId));
         }
 
         boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
@@ -4749,6 +4761,7 @@
                 mPlatformCompat.resetReporting(app.info);
             }
             final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
+            app.mProfile.mLastCpuDelayTime.set(app.getCpuDelayTime());
             if (app.getIsolatedEntryPoint() != null) {
                 // This is an isolated process which should just call an entry point instead of
                 // being bound to an application.
@@ -4786,9 +4799,10 @@
                         app.getStartElapsedTime(), app.getStartUptime());
             }
 
-            Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+            Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
             msg.obj = app;
-            mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+            msg.arg1 = BIND_APPLICATION_TIMEOUT;
+            mHandler.sendMessageDelayed(msg, msg.arg1 /* BIND_APPLICATION_TIMEOUT */);
             mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
 
             if (profilerInfo != null) {
@@ -4828,6 +4842,7 @@
             if (!mConstants.mEnableWaitForFinishAttachApplication) {
                 finishAttachApplicationInner(startSeq, callingUid, pid);
             }
+            maybeSendBootCompletedLocked(app);
         } catch (Exception e) {
             // We need kill the process group here. (b/148588589)
             Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
@@ -4865,7 +4880,8 @@
         }
 
         if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
-            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
+            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app);
+            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);
         } else {
             Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
                     + ". Uid: " + uid);
@@ -5002,6 +5018,35 @@
         }
     }
 
+    private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) {
+        // Similar logic as the broadcast delivery timeout:
+        // instead of immediately triggering an ANR, extend the timeout by
+        // the amount of time the process was runnable-but-waiting; we're
+        // only willing to do this once before triggering an hard ANR.
+        final long cpuDelayTime = app.getCpuDelayTime() - app.mProfile.mLastCpuDelayTime.get();
+        final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
+
+        if (hardTimeoutMillis == 0) {
+            handleBindApplicationTimeoutHard(app);
+            return;
+        }
+
+        Slog.i(TAG, "Extending process start timeout by " + hardTimeoutMillis + "ms for " + app);
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplicationTimeSoft "
+                + app.processName + "(" + app.getPid() + ")");
+        final Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);
+        mHandler.sendMessageDelayed(msg, hardTimeoutMillis);
+    }
+
+    private void handleBindApplicationTimeoutHard(ProcessRecord app) {
+        final String anrMessage;
+        synchronized (app) {
+            anrMessage = "Process " + app + " failed to complete startup";
+        }
+
+        mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+    }
+
     /**
      * @return The last part of the string of an intent's action.
      */
@@ -5024,6 +5069,45 @@
         }
     }
 
+    /**
+     * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
+     */
+    private void maybeSendBootCompletedLocked(ProcessRecord app) {
+        // Nothing to do if it wasn't previously stopped
+        if (!android.content.pm.Flags.stayStopped() || !app.wasForceStopped()) return;
+
+        // Send LOCKED_BOOT_COMPLETED, if necessary
+        if (app.getApplicationInfo().isEncryptionAware()) {
+            sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED),
+                    REASON_LOCKED_BOOT_COMPLETED);
+        }
+        // Send BOOT_COMPLETED if the user is unlocked
+        if (StorageManager.isUserKeyUnlocked(app.userId)) {
+            sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
+                    REASON_BOOT_COMPLETED);
+        }
+        app.setWasForceStopped(false);
+    }
+
+    /** Send a boot_completed broadcast to app */
+    private void sendBootBroadcastToAppLocked(ProcessRecord app, Intent intent,
+            @PowerExemptionManager.ReasonCode int reason) {
+        intent.setPackage(app.info.packageName);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, app.userId);
+        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,
+                false, MY_PID, SYSTEM_UID,
+                SYSTEM_UID, MY_PID, app.userId);
+    }
+
     @Override
     public void showBootMessage(final CharSequence msg, final boolean always) {
         if (Binder.getCallingUid() != myUid()) {
@@ -5126,10 +5210,14 @@
                         public void performReceive(Intent intent, int resultCode,
                                 String data, Bundle extras, boolean ordered,
                                 boolean sticky, int sendingUser) {
-                            synchronized (mProcLock) {
-                                mAppProfiler.requestPssAllProcsLPr(
-                                        SystemClock.uptimeMillis(), true, false);
-                            }
+                            mBootCompletedTimestamp = SystemClock.uptimeMillis();
+                            // Defer the full Pss collection as the system is really busy now.
+                            mHandler.postDelayed(() -> {
+                                synchronized (mProcLock) {
+                                    mAppProfiler.requestPssAllProcsLPr(
+                                            SystemClock.uptimeMillis(), true, false);
+                                }
+                            }, mConstants.FULL_PSS_MIN_INTERVAL);
                         }
                     });
             maybeLogUserspaceRebootEvent();
@@ -5430,7 +5518,7 @@
                             + " Calling package: " + packageName + "; intent: " + intent
                             + "; options: " + options);
                 }
-                target.send(code, intent, resolvedType, allowlistToken, null,
+                target.send(code, intent, resolvedType, null, null,
                         requiredPermission, options);
             } catch (RemoteException e) {
             }
@@ -9630,9 +9718,29 @@
     public ParceledListSlice<ApplicationStartInfo> getHistoricalProcessStartReasons(
             String packageName, int maxNum, int userId) {
         enforceNotIsolatedCaller("getHistoricalProcessStartReasons");
+        // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+            throw new IllegalArgumentException("Unsupported userId");
+        }
+
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
+                "getHistoricalProcessStartReasons", null);
 
         final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>();
-
+        if (!TextUtils.isEmpty(packageName)) {
+            final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
+                        "getHistoricalProcessStartReasons");
+            if (uid != INVALID_UID) {
+                mProcessList.mAppStartInfoTracker.getStartInfo(
+                        packageName, userId, callingPid, maxNum, results);
+            }
+        } else {
+            // If no package name is given, use the caller's uid as the filter uid.
+            mProcessList.mAppStartInfoTracker.getStartInfo(
+                    packageName, callingUid, callingPid, maxNum, results);
+        }
         return new ParceledListSlice<ApplicationStartInfo>(results);
     }
 
@@ -9641,6 +9749,14 @@
     public void setApplicationStartInfoCompleteListener(
             IApplicationStartInfoCompleteListener listener, int userId) {
         enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener");
+
+        // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+            throw new IllegalArgumentException("Unsupported userId");
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid);
     }
 
 
@@ -9654,6 +9770,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
+        mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
     }
 
     @Override
@@ -9955,6 +10072,8 @@
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
+                mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+                pw.println("-------------------------------------------------------------------------------");
                 mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             }
             if (dumpPackage == null) {
@@ -10351,6 +10470,12 @@
                 LockGuard.dump(fd, pw, args);
             } else if ("users".equals(cmd)) {
                 dumpUsers(pw);
+            } else if ("start-info".equals(cmd)) {
+                if (opti < args.length) {
+                    dumpPackage = args[opti];
+                    opti++;
+                }
+                mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
             } else if ("exit-info".equals(cmd)) {
                 if (opti < args.length) {
                     dumpPackage = args[opti];
@@ -14248,8 +14373,7 @@
     private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
             int callingUid, int[] users, int[] broadcastAllowList) {
         // TODO: come back and remove this assumption to triage all broadcasts
-        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING
-                | FILTER_OUT_QUARANTINED_COMPONENTS;
+        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
 
         List<ResolveInfo> receivers = null;
         HashSet<ComponentName> singleUserReceivers = null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a057f32..69bf612 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -272,6 +272,8 @@
                     return runSetWatchHeap(pw);
                 case "clear-watch-heap":
                     return runClearWatchHeap(pw);
+                case "clear-start-info":
+                    return runClearStartInfo(pw);
                 case "clear-exit-info":
                     return runClearExitInfo(pw);
                 case "bug-report":
@@ -1339,6 +1341,31 @@
         return 0;
     }
 
+    int runClearStartInfo(PrintWriter pw) throws RemoteException {
+        mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
+                "runClearStartInfo()");
+        String opt;
+        int userId = UserHandle.USER_CURRENT;
+        String packageName = null;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                packageName = opt;
+            }
+        }
+        if (userId == UserHandle.USER_CURRENT) {
+            UserInfo user = mInterface.getCurrentUser();
+            if (user == null) {
+                return -1;
+            }
+            userId = user.id;
+        }
+        mInternal.mProcessList.mAppStartInfoTracker
+                .clearHistoryProcessStartInfo(packageName, userId);
+        return 0;
+    }
+
     int runClearExitInfo(PrintWriter pw) throws RemoteException {
         mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
                 "runClearExitInfo()");
@@ -4090,6 +4117,7 @@
             pw.println("    s[ervices] [COMP_SPEC ...]: service state");
             pw.println("    allowed-associations: current package association restrictions");
             pw.println("    as[sociations]: tracked app associations");
+            pw.println("    start-info [PACKAGE_NAME]: historical process start information");
             pw.println("    exit-info [PACKAGE_NAME]: historical process exit information");
             pw.println("    lmk: stats on low memory killer");
             pw.println("    lru: raw LRU process list");
@@ -4265,6 +4293,8 @@
             pw.println("      above <HEAP-LIMIT> then a heap dump is collected for the user to report.");
             pw.println("  clear-watch-heap");
             pw.println("      Clear the previously set-watch-heap.");
+            pw.println("  clear-start-info [--user <USER_ID> | all | current] [package]");
+            pw.println("      Clear the process start-info for given package");
             pw.println("  clear-exit-info [--user <USER_ID> | all | current] [package]");
             pw.println("      Clear the process exit-info for given package");
             pw.println("  bug-report [--progress | --telephony]");
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
index 9ba49ce..3e17930 100644
--- a/services/core/java/com/android/server/am/AnrTimer.java
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -28,6 +28,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.text.TextUtils;
+import android.text.format.TimeMigrationUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -44,7 +45,6 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -150,7 +150,7 @@
         /** A partial stack that localizes the caller of the operation. */
         final StackTraceElement[] stack;
         /** The date, in local time, the error was created. */
-        final String date;
+        final long timestamp;
 
         Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
                 @NonNull StackTraceElement[] stack, @NonNull String arg) {
@@ -159,7 +159,7 @@
             this.tag = tag;
             this.stack = stack;
             this.arg = arg;
-            this.date = new Date().toString();
+            this.timestamp = SystemClock.elapsedRealtime();
         }
     }
 
@@ -347,20 +347,23 @@
          * main Looper.
          */
         @NonNull
-        Handler getHandler(@NonNull Handler.Callback callback) {
+        Handler newHandler(@NonNull Handler.Callback callback) {
             Looper looper = mReferenceHandler.getLooper();
             if (looper == null) looper = Looper.getMainLooper();
             return new Handler(looper, callback);
-        };
+        }
 
-        /** Return a CpuTracker. */
+        /**
+         * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
+         * for unit tests.
+         **/
         @NonNull
-        CpuTracker getTracker() {
+        CpuTracker newTracker() {
             return new CpuTracker();
         }
 
         /** Return true if the feature is enabled. */
-        boolean getFeatureEnabled() {
+        boolean isFeatureEnabled() {
             return anrTimerServiceEnabled();
         }
     }
@@ -401,8 +404,8 @@
         /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
         @VisibleForTesting
         HandlerTimerService(@NonNull Injector injector) {
-            mHandler = injector.getHandler(this::expires);
-            mCpu = injector.getTracker();
+            mHandler = injector.newHandler(this::expires);
+            mCpu = injector.newTracker();
         }
 
         /** Post a message with the specified timeout.  The timer is not modified. */
@@ -513,7 +516,26 @@
     private final FeatureSwitch mFeature;
 
     /**
-     * The common constructor.  A null injector results in a normal, production timer.
+     * Create one AnrTimer instance.  The instance is given a handler and a "what".  Individual
+     * timers are started with {@link #start}.  If a timer expires, then a {@link Message} is sent
+     * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+     * to the timer key.
+     *
+     * AnrTimer instances have a label, which must be unique.  The label is used for reporting and
+     * debug.
+     *
+     * If an individual timer expires internally, and the "extend" parameter is true, then the
+     * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+     * the client.  The extension policy is not part of the instance.
+     *
+     * This method accepts an {@link #Injector} to tune behavior for testing.  This method should
+     * not be called directly by regular clients.
+     *
+     * @param handler The handler to which the expiration message will be delivered.
+     * @param what The "what" parameter for the expiration message.
+     * @param label A name for this instance.
+     * @param extend A flag to indicate if expired timers can be granted extensions.
+     * @param injector An {@link #Injector} to tune behavior for testing.
      */
     @VisibleForTesting
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
@@ -522,7 +544,7 @@
         mWhat = what;
         mLabel = label;
         mExtend = extend;
-        boolean enabled = injector.getFeatureEnabled();
+        boolean enabled = injector.isFeatureEnabled();
         if (!enabled) {
             mFeature = new FeatureDisabled();
             mTimerService = null;
@@ -538,14 +560,25 @@
     }
 
     /**
-     * Create one timer instance for production.  The client can ask for extensible timeouts.
+     * Create an AnrTimer instance with the default {@link #Injector}.  See {@link AnrTimer(Handler,
+     * int, String, boolean, Injector} for a functional description.
+     *
+     * @param handler The handler to which the expiration message will be delivered.
+     * @param what The "what" parameter for the expiration message.
+     * @param label A name for this instance.
+     * @param extend A flag to indicate if expired timers can be granted extensions.
      */
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
         this(handler, what, label, extend, new Injector(handler));
     }
 
     /**
-     * Create one timer instance for production.  There are no extensible timeouts.
+     * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled.
+     * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description.
+     *
+     * @param handler The handler to which the expiration message will be delivered.
+     * @param what The "what" parameter for the expiration message.
+     * @param label A name for this instance.
      */
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
         this(handler, what, label, false);
@@ -555,6 +588,8 @@
      * Return true if the service is enabled on this instance.  Clients should use this method to
      * decide if the feature is enabled, and not read the flags directly.  This method should be
      * deleted if and when the feature is enabled permanently.
+     *
+     * @return true if the service is flag-enabled.
      */
     boolean serviceEnabled() {
         return mFeature.enabled();
@@ -642,7 +677,7 @@
     }
 
     /**
-     * Report something about a timer.
+     * Generate a log message for a timer.
      */
     private void report(@NonNull Timer timer, @NonNull String msg) {
         Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
@@ -654,9 +689,13 @@
      */
     private abstract class FeatureSwitch {
         abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+
         abstract boolean cancel(@NonNull V arg);
+
         abstract boolean accept(@NonNull V arg);
+
         abstract boolean discard(@NonNull V arg);
+
         abstract boolean enabled();
     }
 
@@ -666,6 +705,7 @@
      */
     private class FeatureDisabled extends FeatureSwitch {
         /** Start a timer by sending a message to the client's handler. */
+        @Override
         boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Message msg = mHandler.obtainMessage(mWhat, arg);
             mHandler.sendMessageDelayed(msg, timeoutMs);
@@ -673,22 +713,26 @@
         }
 
         /** Cancel a timer by removing the message from the client's handler. */
+        @Override
         boolean cancel(@NonNull V arg) {
             mHandler.removeMessages(mWhat, arg);
             return true;
         }
 
         /** accept() is a no-op when the feature is disabled. */
+        @Override
         boolean accept(@NonNull V arg) {
             return true;
         }
 
         /** discard() is a no-op when the feature is disabled. */
+        @Override
         boolean discard(@NonNull V arg) {
             return true;
         }
 
         /** The feature is not enabled. */
+        @Override
         boolean enabled() {
             return false;
         }
@@ -703,16 +747,17 @@
         /**
          * Start a timer.
          */
+        @Override
         boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
             synchronized (mLock) {
                 Timer old = mTimerMap.get(arg);
+                // There is an existing timer.  If the timer was running, then cancel the running
+                // timer and restart it.  If the timer was expired record a protocol error and
+                // discard the expired timer.
                 if (old != null) {
-                    // There is an existing timer.  This is a protocol error in the client.
-                    // Record the error and then clean up by canceling running timers and
-                    // discarding expired timers.
-                    restartedLocked(old.status, arg);
                     if (old.status == TIMER_EXPIRED) {
+                      restartedLocked(old.status, arg);
                         discard(arg);
                     } else {
                         cancel(arg);
@@ -735,6 +780,7 @@
         /**
          * Cancel a timer.  Return false if the timer was not found.
          */
+        @Override
         boolean cancel(@NonNull V arg) {
             synchronized (mLock) {
                 Timer timer = removeLocked(arg);
@@ -755,6 +801,7 @@
          * Accept a timer in the framework-level handler.  The timeout has been accepted and the
          * timeout handler is executing.  Return false if the timer was not found.
          */
+        @Override
         boolean accept(@NonNull V arg) {
             synchronized (mLock) {
                 Timer timer = removeLocked(arg);
@@ -775,6 +822,7 @@
          * longer interesting.  No statistics are collected.  Return false if the time was not
          * found.
          */
+        @Override
         boolean discard(@NonNull V arg) {
             synchronized (mLock) {
                 Timer timer = removeLocked(arg);
@@ -791,40 +839,58 @@
         }
 
         /** The feature is enabled. */
+        @Override
         boolean enabled() {
             return true;
         }
     }
 
     /**
-     * Start a timer associated with arg.  If a timer already exists with the same arg, then that
-     * timer is canceled and a new timer is created.  This returns false if the timer cannot be
-     * created.
+     * Start a timer associated with arg.  The same object must be used to cancel, accept, or
+     * discard a timer later.  If a timer already exists with the same arg, then the existing timer
+     * is canceled and a new timer is created.
+     *
+     * @param arg The key by which the timer is known.  This is never examined or modified.
+     * @param pid The Linux process ID of the target being timed.
+     * @param uid The Linux user ID of the target being timed.
+     * @param timeoutMs The timer timeout, in milliseconds.
+     * @return true if the timer was successfully created.
      */
     boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
         return mFeature.start(arg, pid, uid, timeoutMs);
     }
 
     /**
-     * Cancel a running timer and remove it from any list.  This returns true if the timer was
-     * found and false otherwise.  It is not an error to cancel a non-existent timer.  It is also
-     * not an error to cancel an expired timer.
+     * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
+     * expired, the call is treated as a discard.  No errors are reported if the timer does not
+     * exist or if the timer has expired.
+     *
+     * @return true if the timer was found and was running.
      */
     boolean cancel(@NonNull V arg) {
         return mFeature.cancel(arg);
     }
 
     /**
-     * Accept an expired timer.  This returns false if the timer was not found or if the timer was
-     * not expired.
+     * Accept the expired timer associated with arg.  This indicates that the caller considers the
+     * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
+     * an error to accept a running timer, however the running timer will be canceled.
+     *
+     * @return true if the timer was found and was expired.
      */
     boolean accept(@NonNull V arg) {
         return mFeature.accept(arg);
     }
 
     /**
-     * Discard an expired timer.  This returns false if the timer was not found or if the timer was
-     * not expired.
+     * Discard the expired timer associated with arg.  This indicates that the caller considers the
+     * timer expiration to be a false ANR.  ((See {@link #accept} for an alternate response.)  One
+     * reason to discard an expired timer is if the process being timed was also being debugged:
+     * such a process could be stopped at a breakpoint and its failure to respond would not be an
+     * error.  It is an error to discard a running timer, however the running timer will be
+     * canceled.
+     *
+     * @return true if the timer was found and was expired.
      */
     boolean discard(@NonNull V arg) {
         return mFeature.discard(arg);
@@ -913,7 +979,10 @@
     private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
         ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
                 err.issue, err.arg);
-        ipw.format("    date:%s\n", err.date);
+
+        final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+        final long etime = offset + err.timestamp;
+        ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
         ipw.increaseIndent();
         for (int i = 0; i < err.stack.length; i++) {
             ipw.println("    " + err.stack[i].toString());
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
new file mode 100644
index 0000000..edca74f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -0,0 +1,989 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.ActivityOptions;
+import android.app.ApplicationStartInfo;
+import android.app.Flags;
+import android.app.IApplicationStartInfoCompleteListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.icu.text.SimpleDateFormat;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ProcessMap;
+import com.android.server.IoThread;
+import com.android.server.ServiceThread;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+
+/** A class to manage all the {@link android.app.ApplicationStartInfo} records. */
+public final class AppStartInfoTracker {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppStartInfoTracker" : TAG_AM;
+
+    /** Interval of persisting the app start info to persistent storage. */
+    private static final long APP_START_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
+
+    /** These are actions that the forEach* should take after each iteration */
+    private static final int FOREACH_ACTION_NONE = 0;
+    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 String APP_START_STORE_DIR = "procstartstore";
+
+    @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo";
+
+    private final Object mLock = new Object();
+
+    private boolean mEnabled = false;
+
+    /** Initialized in {@link #init} and read-only after that. */
+    private ActivityManagerService mService;
+
+    /** Initialized in {@link #init} and read-only after that. */
+    private Handler mHandler;
+
+    /** The task to persist app process start info */
+    @GuardedBy("mLock")
+    private Runnable mAppStartInfoPersistTask = null;
+
+    /**
+     * Last time(in ms) since epoch that the app start info was persisted into persistent storage.
+     */
+    @GuardedBy("mLock")
+    private long mLastAppStartInfoPersistTimestamp = 0L;
+
+    /**
+     * Retention policy: keep up to X historical start info per package.
+     *
+     * <p>Initialized in {@link #init} and read-only after that. No lock is needed.
+     */
+    private int mAppStartInfoHistoryListSize;
+
+    @GuardedBy("mLock")
+    private final ProcessMap<AppStartInfoContainer> mData;
+
+    /** UID as key. */
+    @GuardedBy("mLock")
+    private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks;
+
+    /**
+     * Whether or not we've loaded the historical app process start info from persistent storage.
+     */
+    @VisibleForTesting AtomicBoolean mAppStartInfoLoaded = new AtomicBoolean();
+
+    /** Temporary list being used to filter/sort intermediate results in {@link #getStartInfo}. */
+    @GuardedBy("mLock")
+    final ArrayList<ApplicationStartInfo> mTmpStartInfoList = new ArrayList<>();
+
+    /**
+     * The path to the directory which includes the historical proc start info file as specified in
+     * {@link #mProcStartInfoFile}.
+     */
+    @VisibleForTesting File mProcStartStoreDir;
+
+    /** The path to the historical proc start info file, persisted in the storage. */
+    @VisibleForTesting File mProcStartInfoFile;
+
+    AppStartInfoTracker() {
+        mCallbacks = new SparseArray<>();
+        mData = new ProcessMap<AppStartInfoContainer>();
+    }
+
+    void init(ActivityManagerService service) {
+        mService = service;
+
+        ServiceThread thread =
+                new ServiceThread(TAG + ":handler", THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+
+        mProcStartStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_START_STORE_DIR);
+        if (!FileUtils.createDir(mProcStartStoreDir)) {
+            Slog.e(TAG, "Unable to create " + mProcStartStoreDir);
+            return;
+        }
+        mProcStartInfoFile = new File(mProcStartStoreDir, APP_START_INFO_FILE);
+
+        mAppStartInfoHistoryListSize = APP_START_INFO_HISTORY_LIST_SIZE;
+    }
+
+    void onSystemReady() {
+        mEnabled = Flags.appStartInfo();
+        if (!mEnabled) {
+            return;
+        }
+
+        registerForUserRemoval();
+        registerForPackageRemoval();
+        IoThread.getHandler().post(() -> {
+            loadExistingProcessStartInfo();
+        });
+    }
+
+    void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord,
+            ProcessRecord app) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime());
+            start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+            start.setReason(ApplicationStartInfo.START_REASON_OTHER);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessActivityWarmOrHotStarted(long startTimeNs,
+            ActivityOptions activityOptions, Intent intent) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setIntent(intent);
+            start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+            if (activityOptions != null) {
+                start.setProcessName(activityOptions.getPackageName());
+            }
+            start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+            if (intent != null && intent.getCategories() != null
+                    && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+                start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+            } else {
+                start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
+            }
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessActivityStartedFromRecents(long startTimeNs,
+            ActivityOptions activityOptions) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            if (activityOptions != null) {
+                start.setIntent(activityOptions.getResultData());
+                start.setProcessName(activityOptions.getPackageName());
+            }
+            start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS);
+            start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
+                ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setReason(serviceRecord.permission != null
+                    && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
+                    ? ApplicationStartInfo.START_REASON_JOB
+                    : ApplicationStartInfo.START_REASON_SERVICE);
+            start.setIntent(serviceRecord.intent.getIntent());
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
+                BroadcastRecord broadcast, boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            if (broadcast == null) {
+                start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
+            } else if (broadcast.alarm) {
+                start.setReason(ApplicationStartInfo.START_REASON_ALARM);
+            } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
+                start.setReason(ApplicationStartInfo.START_REASON_PUSH);
+            } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
+                start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
+            } else {
+                start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
+            }
+            start.setIntent(broadcast != null ? broadcast.intent : null);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
+                boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessBackupStart(long startTimeNs, ProcessRecord app,
+                BackupRecord backupRecord, boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setReason(ApplicationStartInfo.START_REASON_BACKUP);
+            addStartInfoLocked(start);
+        }
+    }
+
+    private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) {
+        if (app == null) {
+            return;
+        }
+        final int definingUid = app.getHostingRecord() != null
+                ? app.getHostingRecord().getDefiningUid() : 0;
+        start.setPid(app.getPid());
+        start.setRealUid(app.uid);
+        start.setPackageUid(app.info.uid);
+        start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
+        start.setProcessName(app.processName);
+    }
+
+    void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE);
+    }
+
+    void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
+    }
+
+    void reportFirstFrameTimeNanos(ProcessRecord app, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
+    }
+
+    void reportFullyDrawnTimeNanos(ProcessRecord app, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN);
+    }
+
+    void reportFullyDrawnTimeNanos(String processName, int uid, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(processName, uid, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN);
+    }
+
+    private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
+        addTimestampToStart(app.processName, app.uid, timeNs, key);
+    }
+
+    private void addTimestampToStart(String processName, int uid, long timeNs, int key) {
+        synchronized (mLock) {
+            AppStartInfoContainer container = mData.get(processName, uid);
+            if (container == null) {
+                // Record was not created, discard new data.
+                return;
+            }
+            container.addTimestampToStartLocked(key, timeNs);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private ApplicationStartInfo addStartInfoLocked(ApplicationStartInfo raw) {
+        if (!mAppStartInfoLoaded.get()) {
+            //records added before initial load from storage will be lost.
+            Slog.w(TAG, "Skipping saving the start info due to ongoing loading from storage");
+            return null;
+        }
+
+        final ApplicationStartInfo info = new ApplicationStartInfo(raw);
+
+        AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid());
+        if (container == null) {
+            container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
+            container.mUid = info.getRealUid();
+            mData.put(raw.getProcessName(), raw.getRealUid(), container);
+        }
+        container.addStartInfoLocked(info);
+
+        schedulePersistProcessStartInfo(false);
+
+        return info;
+    }
+
+    /**
+     * Called whenever data is added to a {@link ApplicationStartInfo} object. Checks for
+     * completeness and triggers callback if a callback has been registered and the object
+     * is complete.
+     */
+    private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) {
+        synchronized (mLock) {
+            if (startInfo.getStartupState()
+                    == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
+                ApplicationStartInfoCompleteCallback callback =
+                        mCallbacks.get(startInfo.getRealUid());
+                if (callback != null) {
+                    callback.onApplicationStartInfoComplete(startInfo);
+                }
+            }
+        }
+    }
+
+    void getStartInfo(String packageName, int filterUid, int filterPid,
+            int maxNum, ArrayList<ApplicationStartInfo> results) {
+        if (!mEnabled) {
+            return;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                boolean emptyPackageName = TextUtils.isEmpty(packageName);
+                if (!emptyPackageName) {
+                    // fast path
+                    AppStartInfoContainer container = mData.get(packageName, filterUid);
+                    if (container != null) {
+                        container.getStartInfoLocked(filterPid, maxNum, results);
+                    }
+                } else {
+                    // slow path
+                    final ArrayList<ApplicationStartInfo> list = mTmpStartInfoList;
+                    list.clear();
+                    // get all packages
+                    forEachPackageLocked(
+                            (name, records) -> {
+                                AppStartInfoContainer container = records.get(filterUid);
+                                if (container != null) {
+                                    list.addAll(container.mInfos);
+                                }
+                                return AppStartInfoTracker.FOREACH_ACTION_NONE;
+                            });
+
+                    Collections.sort(
+                            list, (a, b) ->
+                            Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+                    int size = list.size();
+                    if (maxNum > 0) {
+                        size = Math.min(size, maxNum);
+                    }
+                    for (int i = 0; i < size; i++) {
+                        results.add(list.get(i));
+                    }
+                    list.clear();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    final class ApplicationStartInfoCompleteCallback implements DeathRecipient {
+        private final int mUid;
+        private final IApplicationStartInfoCompleteListener mCallback;
+
+        ApplicationStartInfoCompleteCallback(IApplicationStartInfoCompleteListener callback,
+                int uid) {
+            mCallback = callback;
+            mUid = uid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        void onApplicationStartInfoComplete(ApplicationStartInfo startInfo) {
+            try {
+                mCallback.onApplicationStartInfoComplete(startInfo);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+            clearStartInfoCompleteListener(mUid, true);
+        }
+
+        void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            clearStartInfoCompleteListener(mUid, false);
+        }
+    }
+
+    void addStartInfoCompleteListener(
+            final IApplicationStartInfoCompleteListener listener, final int uid) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid));
+        }
+    }
+
+    void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (unlinkDeathRecipient) {
+                ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid);
+                if (callback != null) {
+                    callback.unlinkToDeath();
+                }
+            }
+            mCallbacks.remove(uid);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void forEachPackageLocked(
+            BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback) {
+        if (callback != null) {
+            ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap();
+            for (int i = map.size() - 1; i >= 0; i--) {
+                switch (callback.apply(map.keyAt(i), map.valueAt(i))) {
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        map.removeAt(i);
+                        break;
+                    case FOREACH_ACTION_STOP_ITERATION:
+                        i = 0;
+                        break;
+                    case FOREACH_ACTION_NONE:
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) {
+        ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap();
+        SparseArray<AppStartInfoContainer> array = map.get(packageName);
+        if (array == null) {
+            return;
+        }
+        if (userId == UserHandle.USER_ALL) {
+            mData.getMap().remove(packageName);
+        } else {
+            for (int i = array.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(array.keyAt(i)) == userId) {
+                    array.removeAt(i);
+                    break;
+                }
+            }
+            if (array.size() == 0) {
+                map.remove(packageName);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void removeByUserIdLocked(final int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            mData.getMap().clear();
+            return;
+        }
+        forEachPackageLocked(
+                (packageName, records) -> {
+                    for (int i = records.size() - 1; i >= 0; i--) {
+                        if (UserHandle.getUserId(records.keyAt(i)) == userId) {
+                            records.removeAt(i);
+                            break;
+                        }
+                    }
+                    return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE;
+                });
+    }
+
+    @VisibleForTesting
+    void onUserRemoved(int userId) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            removeByUserIdLocked(userId);
+            schedulePersistProcessStartInfo(true);
+        }
+    }
+
+    @VisibleForTesting
+    void onPackageRemoved(String packageName, int uid, boolean allUsers) {
+        if (!mEnabled) {
+            return;
+        }
+        if (packageName != null) {
+            final boolean removeUid =
+                    TextUtils.isEmpty(mService.mPackageManagerInt.getNameForUid(uid));
+            synchronized (mLock) {
+                removePackageLocked(
+                        packageName,
+                        uid,
+                        removeUid,
+                        allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
+                schedulePersistProcessStartInfo(true);
+            }
+        }
+    }
+
+    private void registerForUserRemoval() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mService.mContext.registerReceiverForAllUsers(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userId < 1) return;
+                        onUserRemoved(userId);
+                    }
+                },
+                filter,
+                null,
+                mHandler);
+    }
+
+    private void registerForPackageRemoval() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        mService.mContext.registerReceiverForAllUsers(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        if (replacing) {
+                            return;
+                        }
+                        int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+                        boolean allUsers =
+                                intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+                        onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers);
+                    }
+                },
+                filter,
+                null,
+                mHandler);
+    }
+
+    /**
+     * Load the existing {@link android.app.ApplicationStartInfo} records from persistent storage.
+     */
+    @VisibleForTesting
+    void loadExistingProcessStartInfo() {
+        if (!mEnabled) {
+            return;
+        }
+        if (!mProcStartInfoFile.canRead()) {
+            // If file can't be read, mark complete so we can begin accepting new records.
+            mAppStartInfoLoaded.set(true);
+            return;
+        }
+
+        FileInputStream fin = null;
+        try {
+            AtomicFile af = new AtomicFile(mProcStartInfoFile);
+            fin = af.openRead();
+            ProtoInputStream proto = new ProtoInputStream(fin);
+            for (int next = proto.nextField();
+                    next != ProtoInputStream.NO_MORE_FIELDS;
+                    next = proto.nextField()) {
+                switch (next) {
+                    case (int) AppsStartInfoProto.LAST_UPDATE_TIMESTAMP:
+                        synchronized (mLock) {
+                            mLastAppStartInfoPersistTimestamp =
+                                    proto.readLong(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP);
+                        }
+                        break;
+                    case (int) AppsStartInfoProto.PACKAGES:
+                        loadPackagesFromProto(proto, next);
+                        break;
+                }
+            }
+        } catch (IOException | IllegalArgumentException | WireTypeMismatchException
+                | ClassNotFoundException e) {
+            Slog.w(TAG, "Error in loading historical app start info from persistent storage: " + e);
+        } finally {
+            if (fin != null) {
+                try {
+                    fin.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        mAppStartInfoLoaded.set(true);
+    }
+
+    private void loadPackagesFromProto(ProtoInputStream proto, long fieldId)
+            throws IOException, WireTypeMismatchException, ClassNotFoundException {
+        long token = proto.start(fieldId);
+        String pkgName = "";
+        for (int next = proto.nextField();
+                next != ProtoInputStream.NO_MORE_FIELDS;
+                next = proto.nextField()) {
+            switch (next) {
+                case (int) AppsStartInfoProto.Package.PACKAGE_NAME:
+                    pkgName = proto.readString(AppsStartInfoProto.Package.PACKAGE_NAME);
+                    break;
+                case (int) AppsStartInfoProto.Package.USERS:
+                    AppStartInfoContainer container =
+                            new AppStartInfoContainer(mAppStartInfoHistoryListSize);
+                    int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS);
+                    synchronized (mLock) {
+                        mData.put(pkgName, uid, container);
+                    }
+                    break;
+            }
+        }
+        proto.end(token);
+    }
+
+    /** Persist the existing {@link android.app.ApplicationStartInfo} records to storage. */
+    @VisibleForTesting
+    void persistProcessStartInfo() {
+        if (!mEnabled) {
+            return;
+        }
+        AtomicFile af = new AtomicFile(mProcStartInfoFile);
+        FileOutputStream out = null;
+        long now = System.currentTimeMillis();
+        try {
+            out = af.startWrite();
+            ProtoOutputStream proto = new ProtoOutputStream(out);
+            proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now);
+            synchronized (mLock) {
+                forEachPackageLocked(
+                        (packageName, records) -> {
+                            long token = proto.start(AppsStartInfoProto.PACKAGES);
+                            proto.write(AppsStartInfoProto.Package.PACKAGE_NAME, packageName);
+                            int uidArraySize = records.size();
+                            for (int j = 0; j < uidArraySize; j++) {
+                                try {
+                                    records.valueAt(j)
+                                        .writeToProto(proto, AppsStartInfoProto.Package.USERS);
+                                } catch (IOException e) {
+                                    Slog.w(TAG, "Unable to write app start info into persistent"
+                                            + "storage: " + e);
+                                }
+                            }
+                            proto.end(token);
+                            return AppStartInfoTracker.FOREACH_ACTION_NONE;
+                        });
+                mLastAppStartInfoPersistTimestamp = now;
+            }
+            proto.flush();
+            af.finishWrite(out);
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to write historical app start info into persistent storage: " + e);
+            af.failWrite(out);
+        }
+        synchronized (mLock) {
+            mAppStartInfoPersistTask = null;
+        }
+    }
+
+    /**
+     * Schedule a task to persist the {@link android.app.ApplicationStartInfo} records to storage.
+     */
+    @VisibleForTesting
+    void schedulePersistProcessStartInfo(boolean immediately) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (mAppStartInfoPersistTask == null || immediately) {
+                if (mAppStartInfoPersistTask != null) {
+                    IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask);
+                }
+                mAppStartInfoPersistTask = this::persistProcessStartInfo;
+                IoThread.getHandler()
+                        .postDelayed(
+                                mAppStartInfoPersistTask,
+                                immediately ? 0 : APP_START_INFO_PERSIST_INTERVAL);
+            }
+        }
+    }
+
+    /** Helper function for testing only. */
+    @VisibleForTesting
+    void clearProcessStartInfo(boolean removeFile) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (mAppStartInfoPersistTask != null) {
+                IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask);
+                mAppStartInfoPersistTask = null;
+            }
+            if (removeFile && mProcStartInfoFile != null) {
+                mProcStartInfoFile.delete();
+            }
+            mData.getMap().clear();
+        }
+    }
+
+    /**
+     * Helper functions for shell command.
+     * > adb shell dumpsys activity clear-start-info [package-name]
+     */
+    void clearHistoryProcessStartInfo(String packageName, int userId) {
+        if (!mEnabled) {
+            return;
+        }
+        Optional<Integer> appId = Optional.empty();
+        if (TextUtils.isEmpty(packageName)) {
+            synchronized (mLock) {
+                removeByUserIdLocked(userId);
+            }
+        } else {
+            final int uid =
+                    mService.mPackageManagerInt.getPackageUid(
+                            packageName, PackageManager.MATCH_ALL, userId);
+            appId = Optional.of(UserHandle.getAppId(uid));
+            synchronized (mLock) {
+                removePackageLocked(packageName, uid, true, userId);
+            }
+        }
+        schedulePersistProcessStartInfo(true);
+    }
+
+    /**
+     * Helper functions for shell command.
+     * > adb shell dumpsys activity start-info [package-name]
+     */
+    void dumpHistoryProcessStartInfo(PrintWriter pw, String packageName) {
+        if (!mEnabled) {
+            return;
+        }
+        pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity start-info)");
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        synchronized (mLock) {
+            pw.println("Last Timestamp of Persistence Into Persistent Storage: "
+                    + sdf.format(new Date(mLastAppStartInfoPersistTimestamp)));
+            if (TextUtils.isEmpty(packageName)) {
+                forEachPackageLocked((name, records) -> {
+                    dumpHistoryProcessStartInfoLocked(pw, "  ", name, records, sdf);
+                    return AppStartInfoTracker.FOREACH_ACTION_NONE;
+                });
+            } else {
+                SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName);
+                if (array != null) {
+                    dumpHistoryProcessStartInfoLocked(pw, "  ", packageName, array, sdf);
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void dumpHistoryProcessStartInfoLocked(PrintWriter pw, String prefix,
+            String packageName, SparseArray<AppStartInfoContainer> array,
+            SimpleDateFormat sdf) {
+        pw.println(prefix + "package: " + packageName);
+        int size = array.size();
+        for (int i = 0; i < size; i++) {
+            pw.println(prefix + "  Historical Process Start for userId=" + array.keyAt(i));
+            array.valueAt(i).dumpLocked(pw, prefix + "    ", sdf);
+        }
+    }
+
+    /** Convenience method to obtain timestamp of beginning of start.*/
+    private static long getStartTimestamp(ApplicationStartInfo startInfo) {
+        return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
+    }
+
+    /** A container class of (@link android.app.ApplicationStartInfo) */
+    final class AppStartInfoContainer {
+        private List<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp.
+        private int mMaxCapacity;
+        private int mUid;
+
+        AppStartInfoContainer(final int maxCapacity) {
+            mInfos = new ArrayList<ApplicationStartInfo>();
+            mMaxCapacity = maxCapacity;
+        }
+
+        @GuardedBy("mLock")
+        void getStartInfoLocked(
+                final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results) {
+            results.addAll(mInfos.size() <= maxNum ? 0 : mInfos.size() - maxNum, mInfos);
+        }
+
+        @GuardedBy("mLock")
+        void addStartInfoLocked(ApplicationStartInfo info) {
+            int size = mInfos.size();
+            if (size >= mMaxCapacity) {
+                // Remove oldest record if size is over max capacity.
+                int oldestIndex = -1;
+                long oldestTimeStamp = Long.MAX_VALUE;
+                for (int i = 0; i < size; i++) {
+                    ApplicationStartInfo startInfo = mInfos.get(i);
+                    if (getStartTimestamp(startInfo) < oldestTimeStamp) {
+                        oldestTimeStamp = getStartTimestamp(startInfo);
+                        oldestIndex = i;
+                    }
+                }
+                if (oldestIndex >= 0) {
+                    mInfos.remove(oldestIndex);
+                }
+                mInfos.remove(0);
+            }
+            mInfos.add(info);
+            Collections.sort(mInfos, (a, b) ->
+                    Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+        }
+
+        @GuardedBy("mLock")
+        void addTimestampToStartLocked(int key, long timestampNs) {
+            int index = mInfos.size() - 1;
+            int startupState = mInfos.get(index).getStartupState();
+            if (startupState == ApplicationStartInfo.STARTUP_STATE_STARTED
+                    || key == ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) {
+                mInfos.get(index).addStartupTimestamp(key, timestampNs);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
+            int size = mInfos.size();
+            for (int i = 0; i < size; i++) {
+                mInfos.get(i).dump(pw, prefix + "  ", "#" + i, sdf);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
+            long token = proto.start(fieldId);
+            proto.write(AppsStartInfoProto.Package.User.UID, mUid);
+            int size = mInfos.size();
+            for (int i = 0; i < size; i++) {
+                mInfos.get(i)
+                        .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+            }
+            proto.end(token);
+        }
+
+        int readFromProto(ProtoInputStream proto, long fieldId)
+                throws IOException, WireTypeMismatchException, ClassNotFoundException {
+            long token = proto.start(fieldId);
+            for (int next = proto.nextField();
+                    next != ProtoInputStream.NO_MORE_FIELDS;
+                    next = proto.nextField()) {
+                switch (next) {
+                    case (int) AppsStartInfoProto.Package.User.UID:
+                        mUid = proto.readInt(AppsStartInfoProto.Package.User.UID);
+                        break;
+                    case (int) AppsStartInfoProto.Package.User.APP_START_INFO:
+                        ApplicationStartInfo info = new ApplicationStartInfo();
+                        info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+                        mInfos.add(info);
+                        break;
+                }
+            }
+            proto.end(token);
+            return mUid;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
index 9b5f18c..710278d 100644
--- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Handler;
@@ -54,6 +56,7 @@
         setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app));
         setTitle("Waiting For Debugger");
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         attrs.setTitle("Waiting For Debugger: " + app.info.processName);
         getWindow().setAttributes(attrs);
     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2249607..9e48b0a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -91,6 +91,7 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
+import android.util.AtomicFile;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.StatsEvent;
@@ -99,8 +100,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.CpuScalingPolicyReader;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
@@ -114,16 +117,22 @@
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.power.optimization.Flags;
+import com.android.server.power.stats.AggregatedPowerStatsConfig;
 import com.android.server.power.stats.BatteryExternalStatsWorker;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor;
+import com.android.server.power.stats.PowerStatsAggregator;
+import com.android.server.power.stats.PowerStatsScheduler;
+import com.android.server.power.stats.PowerStatsStore;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.server.power.stats.wakeups.CpuWakeupStats;
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
@@ -136,6 +145,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -153,20 +163,24 @@
     static final String TAG = "BatteryStatsService";
     static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason";
     static final boolean DBG = false;
-    private static final boolean BATTERY_USAGE_STORE_ENABLED = true;
 
     private static IBatteryStats sService;
 
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
+    private final MonotonicClock mMonotonicClock;
     private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig;
     final BatteryStatsImpl mStats;
     final CpuWakeupStats mCpuWakeupStats;
-    private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+    private final PowerStatsStore mPowerStatsStore;
+    private final PowerStatsAggregator mPowerStatsAggregator;
+    private final PowerStatsScheduler mPowerStatsScheduler;
     private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
     private final Context mContext;
     private final BatteryExternalStatsWorker mWorker;
     private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private final AtomicFile mConfigFile;
+
     private volatile boolean mMonitorEnabled = true;
 
     private native void getRailEnergyPowerStats(RailStats railStats);
@@ -376,6 +390,7 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
 
+        mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));
         mPowerProfile = new PowerProfile(context);
         mCpuScalingPolicies = new CpuScalingPolicyReader().read();
 
@@ -391,23 +406,45 @@
                         .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
                         .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
                         .build();
-        mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this,
-                this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies);
+        mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
+                systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+                mCpuScalingPolicies);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
         mStats.startTrackingSystemServerCpuTime();
 
-        if (BATTERY_USAGE_STORE_ENABLED) {
-            mBatteryUsageStatsStore =
-                    new BatteryUsageStatsStore(context, mStats, systemDir, mHandler);
-        } else {
-            mBatteryUsageStatsStore = null;
-        }
+        AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig();
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
-                mBatteryUsageStatsStore);
+                mPowerStatsStore);
+        mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
+                mStats.getHistory());
+        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+        final long powerStatsAggregationPeriod = context.getResources().getInteger(
+                com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+                aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
+                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);
         mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
+        mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
+    }
+
+    private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(
+                        new CpuAggregatedPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
+        return config;
     }
 
     /**
@@ -466,9 +503,7 @@
      */
     public void onSystemReady() {
         mStats.onSystemReady();
-        if (BATTERY_USAGE_STORE_ENABLED) {
-            mBatteryUsageStatsStore.onSystemReady();
-        }
+        mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());
     }
 
     private final class LocalService extends BatteryStatsInternal {
@@ -639,6 +674,9 @@
 
         // Shutdown the thread we made.
         mWorker.shutdown();
+
+        // To insure continuity, write the monotonic timeshift after writing the last history event
+        mMonotonicClock.write();
     }
 
     public static IBatteryStats getService() {
@@ -892,12 +930,8 @@
                     bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
                     break;
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
-                    if (!BATTERY_USAGE_STORE_ENABLED) {
-                        return StatsManager.PULL_SKIP;
-                    }
-
-                    final long sessionStart = mBatteryUsageStatsStore
-                            .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
+                    final long sessionStart =
+                            getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
                     final long sessionEnd;
                     synchronized (mStats) {
                         sessionEnd = mStats.getStartClockTime();
@@ -910,8 +944,7 @@
                                     .aggregateSnapshots(sessionStart, sessionEnd)
                                     .build();
                     bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
-                    mBatteryUsageStatsStore
-                            .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
+                    setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
                     break;
                 default:
                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -2641,7 +2674,15 @@
     }
 
     private void dumpAggregatedStats(PrintWriter pw) {
-        mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+        mPowerStatsScheduler.aggregateAndDumpPowerStats(pw);
+    }
+
+    private void dumpPowerStatsStore(PrintWriter pw) {
+        mPowerStatsStore.dump(new IndentingPrintWriter(pw, "  "));
+    }
+
+    private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) {
+        mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, "  "));
     }
 
     private void dumpMeasuredEnergyStats(PrintWriter pw) {
@@ -2789,7 +2830,7 @@
                     synchronized (mStats) {
                         mStats.resetAllStatsAndHistoryLocked(
                                 BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-                        mBatteryUsageStatsStore.removeAllSnapshots();
+                        mPowerStatsStore.reset();
                         pw.println("Battery stats and history reset.");
                         noOutput = true;
                     }
@@ -2891,6 +2932,12 @@
                 } else if ("--aggregated".equals(arg)) {
                     dumpAggregatedStats(pw);
                     return;
+                } else if ("--store".equals(arg)) {
+                    dumpPowerStatsStore(pw);
+                    return;
+                } else if ("--store-toc".equals(arg)) {
+                    dumpPowerStatsStoreTableOfContents(pw);
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2951,7 +2998,7 @@
                                 in.unmarshall(raw, 0, raw.length);
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
-                                        mBatteryStatsConfig,
+                                        mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
                                         mCpuScalingPolicies);
@@ -2993,7 +3040,7 @@
                                 in.unmarshall(raw, 0, raw.length);
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
-                                        mBatteryStatsConfig,
+                                        mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
                                         mCpuScalingPolicies);
@@ -3360,6 +3407,52 @@
         }
     }
 
+    private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
+            "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
+
+    /**
+     * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
+     * in persistent file.
+     */
+    public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
+        synchronized (mConfigFile) {
+            Properties props = new Properties();
+            try (InputStream in = mConfigFile.openRead()) {
+                props.load(in);
+            } catch (IOException e) {
+                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+            }
+            props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
+                    String.valueOf(timestamp));
+            FileOutputStream out = null;
+            try {
+                out = mConfigFile.startWrite();
+                props.store(out, "Statsd atom pull timestamps");
+                mConfigFile.finishWrite(out);
+            } catch (IOException e) {
+                mConfigFile.failWrite(out);
+                Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
+     * statsd atom pull.
+     */
+    public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
+        synchronized (mConfigFile) {
+            Properties props = new Properties();
+            try (InputStream in = mConfigFile.openRead()) {
+                props.load(in);
+            } catch (IOException e) {
+                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+            }
+            return Long.parseLong(
+                    props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
+        }
+    }
+
     /**
      * Sets battery AC charger to enabled/disabled, and freezes the battery state.
      */
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 127c5b3..3c56752 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1440,10 +1440,9 @@
                     r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
         }
 
-        // Broadcast is being executed, its package can't be stopped.
         try {
-            mService.mPackageManagerInt.setPackageStoppedState(
-                    r.curComponent.getPackageName(), false, r.userId);
+            mService.mPackageManagerInt.notifyComponentUsed(
+                    r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
                     + r.curComponent.getPackageName() + ": " + e);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a428907..b481697 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -258,7 +258,8 @@
     private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
     private static final int MSG_UID_STATE_CHANGED = 7;
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false.  This constant should be deleted if and
+    // when the flag is fused on.
     private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
 
     private void enqueueUpdateRunningList() {
@@ -274,7 +275,8 @@
                 updateRunningList();
                 return true;
             }
-            // Required when Flags.anrTimerServiceEnabled is false.
+            // Required when Flags.anrTimerServiceEnabled is false.  This case should be deleted if
+            // and when the flag is fused on.
             case MSG_DELIVERY_TIMEOUT_SOFT: {
                 synchronized (mService) {
                     deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
@@ -1169,7 +1171,8 @@
         r.resultTo = null;
     }
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false.  This function can be replaced with a
+    // single call to {@code mAnrTimer.start()} if and when the flag is fused on.
     private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue,
             int softTimeoutMillis) {
         if (mAnrTimer.serviceEnabled()) {
@@ -1181,7 +1184,8 @@
         }
     }
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a
+    // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on.
     private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
         mAnrTimer.cancel(queue);
         if (!mAnrTimer.serviceEnabled()) {
@@ -1189,7 +1193,8 @@
         }
     }
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false.  This function can be deleted entirely
+    // if and when the flag is fused on.
     private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
             int softTimeoutMillis) {
         if (queue.app != null) {
@@ -1977,8 +1982,8 @@
         mService.notifyPackageUse(receiverPackageName,
                 PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
 
-        mService.mPackageManagerInt.setPackageStoppedState(
-                receiverPackageName, false, r.userId);
+        mService.mPackageManagerInt.notifyComponentUsed(
+                receiverPackageName, r.userId, r.callerPackage, r.toString());
     }
 
     private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 4b622f5..903cb7b 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -175,13 +175,15 @@
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
 
         // Register all text aconfig flags.
-        for (String flag : TextFlags.TEXT_ACONFIGS_FLAGS) {
+        for (int i = 0; i < TextFlags.TEXT_ACONFIGS_FLAGS.length; i++) {
+            final String flag = TextFlags.TEXT_ACONFIGS_FLAGS[i];
+            final boolean defaultValue = TextFlags.TEXT_ACONFIG_DEFAULT_VALUE[i];
             sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                     TextFlags.NAMESPACE,
                     flag,
                     TextFlags.getKeyForFlag(flag),
                     boolean.class,
-                    false));  // All aconfig flags are false by default.
+                    defaultValue));
         }
         // add other device configs here...
     }
diff --git a/services/core/java/com/android/server/am/LowMemDetector.java b/services/core/java/com/android/server/am/LowMemDetector.java
index 8f79133..016d3cd 100644
--- a/services/core/java/com/android/server/am/LowMemDetector.java
+++ b/services/core/java/com/android/server/am/LowMemDetector.java
@@ -23,6 +23,7 @@
 import static com.android.internal.app.procstats.ProcessStats.ADJ_NOTHING;
 
 import android.annotation.IntDef;
+import android.os.Trace;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -90,17 +91,31 @@
     private native int waitForPressure();
 
     private final class LowMemThread extends Thread {
+        private boolean mIsTracingMemCriticalLow;
+
+        LowMemThread() {
+            super("LowMemThread");
+        }
+
         public void run() {
 
             while (true) {
                 // sleep waiting for a PSI event
                 int newPressureState = waitForPressure();
+                // PSI event detected
+                boolean isCriticalLowMemory = newPressureState == ADJ_MEM_FACTOR_CRITICAL;
+                if (isCriticalLowMemory && !mIsTracingMemCriticalLow) {
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "criticalLowMemory");
+                } else if (!isCriticalLowMemory && mIsTracingMemCriticalLow) {
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                }
+                mIsTracingMemCriticalLow = isCriticalLowMemory;
                 if (newPressureState == -1) {
                     // epoll broke, tear this down
                     mAvailable = false;
                     break;
                 }
-                // got a PSI event? let's update lowmem info
+                // got an actual PSI event? let's update lowmem info
                 synchronized (mPressureStateLock) {
                     mPressureState = newPressureState;
                 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a451f36..9bba08a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2373,8 +2373,7 @@
             }
         }
 
-        if (ppr.getLastProviderTime() > 0
-                && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+        if ((ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
             if (adj > PREVIOUS_APP_ADJ) {
                 adj = PREVIOUS_APP_ADJ;
                 schedGroup = SCHED_GROUP_BACKGROUND;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4572766..614caffe 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -494,6 +494,10 @@
     @GuardedBy("mService")
     final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
 
+    /** Manages the {@link android.app.ApplicationStartInfo} records. */
+    @GuardedBy("mAppStartInfoTracker")
+    final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
+
     /**
      * The currently running SDK sandbox processes for a uid.
      */
@@ -869,6 +873,8 @@
                                 ApplicationExitInfo.REASON_LOW_MEMORY,
                                 ApplicationExitInfo.SUBREASON_OOM_KILL,
                                 "oom");
+
+                            oomKill.logKillOccurred();
                         }
                     }
                 }
@@ -954,12 +960,14 @@
                         mSystemServerSocketForZygote.getFileDescriptor(),
                         EVENT_INPUT, this::handleZygoteMessages);
             }
+            mAppStartInfoTracker.init(mService);
             mAppExitInfoTracker.init(mService);
             mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper());
         }
     }
 
     void onSystemReady() {
+        mAppStartInfoTracker.onSystemReady();
         mAppExitInfoTracker.onSystemReady();
     }
 
@@ -1439,7 +1447,7 @@
     }
 
     public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test,
-            boolean sleeping, long now) {
+            boolean sleeping, long now, long earliest) {
         boolean first;
         float scalingFactor;
         final int memState = sProcStateToProcMem[procState];
@@ -1470,7 +1478,7 @@
         if (delay > PSS_MAX_INTERVAL) {
             delay = PSS_MAX_INTERVAL;
         }
-        return now + delay;
+        return Math.max(now + delay, earliest);
     }
 
     long getMemLevel(int adjustment) {
@@ -3255,6 +3263,17 @@
                 hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
         final ProcessStateRecord state = r.mState;
 
+        // Check if we should mark the processrecord for first launch after force-stopping
+        if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+            final boolean wasPackageEverLaunched = mService.getPackageManagerInternal()
+                    .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+            // If the package was launched in the past but is currently stopped, only then it
+            // should be considered as stopped after use. Do not mark it if it's the first launch.
+            if (wasPackageEverLaunched) {
+                r.setWasForceStopped(true);
+            }
+        }
+
         if (!isolated && !isSdkSandbox
                 && userId == UserHandle.USER_SYSTEM
                 && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index db74f1a..354f3d3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,6 +23,7 @@
 import android.app.ProcessMemoryState.HostingComponentType;
 import android.content.pm.ApplicationInfo;
 import android.os.Debug;
+import android.os.Process;
 import android.os.SystemClock;
 import android.util.DebugUtils;
 import android.util.TimeUtils;
@@ -142,6 +143,11 @@
     final AtomicLong mCurCpuTime = new AtomicLong(0);
 
     /**
+     * How long the process has spent on waiting in the runqueue since fork.
+     */
+    final AtomicLong mLastCpuDelayTime = new AtomicLong(0);
+
+    /**
      * Last selected memory trimming level.
      */
     @CompositeRWLock({"mService", "mProcLock"})
@@ -266,15 +272,17 @@
                         origBase.makeInactive();
                     }
                     final ApplicationInfo info = mApp.info;
+                    final int attributionUid = getUidForAttribution(mApp);
                     final ProcessState baseProcessTracker = tracker.getProcessStateLocked(
-                            info.packageName, info.uid, info.longVersionCode, mApp.processName);
+                            info.packageName, attributionUid, info.longVersionCode,
+                            mApp.processName);
                     setBaseProcessTracker(baseProcessTracker);
                     baseProcessTracker.makeActive();
                     pkgList.forEachPackage((pkgName, holder) -> {
                         if (holder.state != null && holder.state != origBase) {
                             holder.state.makeInactive();
                         }
-                        tracker.updateProcessStateHolderLocked(holder, pkgName, mApp.info.uid,
+                        tracker.updateProcessStateHolderLocked(holder, pkgName, attributionUid,
                                 mApp.info.longVersionCode, mApp.processName);
                         if (holder.state != baseProcessTracker) {
                             holder.state.makeActive();
@@ -531,7 +539,7 @@
                     tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss);
                     pkgList.forEachPackageProcessStats(holder ->
                             FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
-                                mApp.info.uid,
+                                getUidForAttribution(mApp),
                                 holder.state.getName(),
                                 holder.state.getPackage(),
                                 mLastCachedPss,
@@ -570,7 +578,11 @@
 
     @GuardedBy("mProfilerLock")
     long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) {
-        return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now);
+        return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now,
+                // Cap the Pss time to make sure no Pss is collected during the very few
+                // minutes after the system is boot, given the system is already busy.
+                Math.max(mService.mBootCompletedTimestamp, mService.mLastIdleTime)
+                + mService.mConstants.FULL_PSS_MIN_INTERVAL);
     }
 
     private static void commitNextPssTime(ProcStateMemTracker tracker) {
@@ -586,6 +598,21 @@
         tracker.mPendingMemState = -1;
     }
 
+    /**
+     * Returns the uid that should be used for attribution purposes in profiling / stats.
+     *
+     * In most cases this returns the uid of the process itself. For isolated processes though,
+     * since the process uid is dynamically allocated and can't easily be traced back to the app,
+     * for attribution we use the app package uid.
+     */
+    private static int getUidForAttribution(ProcessRecord processRecord) {
+        if (Process.isIsolatedUid(processRecord.uid)) {
+            return processRecord.info.uid;
+        } else {
+            return processRecord.uid;
+        }
+    }
+
     @GuardedBy("mProfilerLock")
     int getPid() {
         return mPid;
diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java
index 751e8a82..9b72a3a 100644
--- a/services/core/java/com/android/server/am/ProcessProviderRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java
@@ -34,7 +34,7 @@
     /**
      * The last time someone else was using a provider in this process.
      */
-    private long mLastProviderTime;
+    private long mLastProviderTime = Long.MIN_VALUE;
 
     /**
      * class (String) -> ContentProviderRecord.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3771c05..2c6e598 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -438,6 +438,9 @@
 
     final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
 
+    /** Whether the app was launched from a stopped state and is being unstopped. */
+    volatile boolean mWasForceStopped;
+
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
             long startUptime, long startElapsedTime) {
         this.mStartUid = startUid;
@@ -511,8 +514,8 @@
         pw.print(prefix); pw.print("pid="); pw.println(mPid);
         pw.print(prefix); pw.print("lastActivityTime=");
         TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
-        pw.print(prefix); pw.print("startUptimeTime=");
-        TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw);
+        pw.print(prefix); pw.print("startUpTime=");
+        TimeUtils.formatDuration(mStartUptime, nowUptime, pw);
         pw.print(prefix); pw.print("startElapsedTime=");
         TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw);
         pw.println();
@@ -1602,4 +1605,12 @@
     List<ProcessRecord> getLruProcessList() {
         return mService.mProcessList.getLruProcessesLOSP();
     }
+
+    public void setWasForceStopped(boolean stopped) {
+        mWasForceStopped = stopped;
+    }
+
+    public boolean wasForceStopped() {
+        return mWasForceStopped;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index a165e88..f5f2b10 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -347,8 +347,10 @@
         mHasAboveClient = false;
         for (int i = mConnections.size() - 1; i >= 0; i--) {
             ConnectionRecord cr = mConnections.valueAt(i);
-            if (cr.binding.service.app.mServices != this
-                    && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+
+            final boolean isSameProcess = cr.binding.service.app != null
+                    && cr.binding.service.app.mServices == this;
+            if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
                 mHasAboveClient = true;
                 break;
             }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index a9c388c..27c0876 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -280,7 +280,7 @@
      * The last time the process was in the TOP state or greater.
      */
     @GuardedBy("mService")
-    private long mLastTopTime;
+    private long mLastTopTime = Long.MIN_VALUE;
 
     /**
      * Is this an empty background process?
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 1ba1f55..439fe0b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -118,10 +118,12 @@
     // The list is sorted.
     @VisibleForTesting
     static final String[] sDeviceConfigAconfigScopes = new String[] {
+        "accessibility",
         "android_core_networking",
         "angle",
         "arc_next",
         "bluetooth",
+        "build",
         "biometrics_framework",
         "biometrics_integration",
         "camera_platform",
@@ -135,23 +137,30 @@
         "context_hub",
         "core_experiments_team_internal",
         "core_graphics",
+        "game",
         "haptics",
         "hardware_backed_security_mainline",
         "input",
         "machine_learning",
+        "mainline_modularization",
         "mainline_sdk",
         "media_audio",
         "media_drm",
         "media_solutions",
         "nfc",
+        "pdf_viewer",
         "pixel_audio_android",
         "pixel_system_sw_touch",
         "pixel_watch",
         "platform_security",
         "power",
         "preload_safety",
+        "privacy_infra_policy",
+        "resource_manager",
         "responsible_apis",
         "rust",
+        "safety_center",
+        "sensors",
         "system_performance",
         "test_suites",
         "text",
@@ -159,13 +168,23 @@
         "tv_system_ui",
         "vibrator",
         "virtual_devices",
+        "wear_calling_messaging",
+        "wear_connectivity",
+        "wear_esim_carriers",
         "wear_frameworks",
+        "wear_health_services",
+        "wear_media",
+        "wear_offload",
+        "wear_security",
         "wear_system_health",
         "wear_systems",
         "window_surfaces",
-        "windowing_frontend"
+        "windowing_frontend",
     };
 
+    public static final String NAMESPACE_REBOOT_STAGING = "staged";
+    public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
+
     private final String[] mGlobalSettings;
 
     private final String[] mDeviceConfigScopes;
@@ -261,6 +280,22 @@
                         }
                     });
         }
+
+        // add sys prop sync callback for staged flag values
+        DeviceConfig.addOnPropertiesChangedListener(
+            NAMESPACE_REBOOT_STAGING,
+            AsyncTask.THREAD_POOL_EXECUTOR,
+            (DeviceConfig.Properties properties) -> {
+              String scope = properties.getNamespace();
+              for (String key : properties.getKeyset()) {
+                String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
+                if (aconfigPropertyName == null) {
+                    log("unable to construct system property for " + scope + "/" + key);
+                    return;
+                }
+                setProperty(aconfigPropertyName, properties.getString(key, null));
+              }
+            });
     }
 
     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -332,6 +367,35 @@
     }
 
     /**
+     * system property name constructing rule for staged aconfig flags, the flag name
+     * is in the form of [namespace]*[actual flag name], we should push the following
+     * to system properties
+     * "next_boot.[actual sys prop name]".
+     * If the name contains invalid characters or substrings for system property name,
+     * will return null.
+     * @param flagName
+     * @return
+     */
+    @VisibleForTesting
+    static String makeAconfigFlagStagedPropertyName(String flagName) {
+        int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+        if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+            log("invalid staged flag: " + flagName);
+            return null;
+        }
+
+        String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+                flagName.substring(0, idx), flagName.substring(idx+1));
+
+        if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+                || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+            return null;
+        }
+
+        return propertyName;
+    }
+
+    /**
      * system property name constructing rule for aconfig flags:
      * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
      * If the name contains invalid characters or substrings for system property name,
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index de4ad20..728bace 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -658,8 +658,8 @@
         mInjector.getUserJourneyLogger()
                 .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
                 EVENT_STATE_BEGIN);
-        // If the user key hasn't been unlocked yet, we cannot proceed.
-        if (!StorageManager.isUserKeyUnlocked(userId)) return false;
+        // If the user's CE storage hasn't been unlocked yet, we cannot proceed.
+        if (!StorageManager.isCeStorageUnlocked(userId)) return false;
         synchronized (mLock) {
             // Do not proceed if unexpected state or a stale user
             if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
@@ -674,8 +674,8 @@
 
         // Call onBeforeUnlockUser on a worker thread that allows disk I/O
         FgThread.getHandler().post(() -> {
-            if (!StorageManager.isUserKeyUnlocked(userId)) {
-                Slogf.w(TAG, "User key got locked unexpectedly, leaving user locked.");
+            if (!StorageManager.isCeStorageUnlocked(userId)) {
+                Slogf.w(TAG, "User's CE storage got locked unexpectedly, leaving user locked.");
                 return;
             }
 
@@ -709,8 +709,8 @@
     private void finishUserUnlocked(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKED, userId);
-        // Only keep marching forward if user is actually unlocked
-        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+        // Only keep marching forward if the user's CE storage is unlocked.
+        if (!StorageManager.isCeStorageUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
@@ -796,8 +796,8 @@
         if (userInfo == null) {
             return;
         }
-        // Only keep marching forward if user is actually unlocked
-        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+        // Only keep marching forward if the user's CE storage is unlocked.
+        if (!StorageManager.isCeStorageUnlocked(userId)) return;
 
         // Remember that we logged in
         mInjector.getUserManager().onUserLoggedIn(userId);
@@ -1330,7 +1330,7 @@
             }
             try {
                 Slogf.i(TAG, "Locking CE storage for user #" + userId);
-                mInjector.getStorageManager().lockUserKey(userId);
+                mInjector.getStorageManager().lockCeStorage(userId);
             } catch (RemoteException re) {
                 throw re.rethrowAsRuntimeException();
             }
@@ -1946,8 +1946,8 @@
         }
 
         UserState uss;
-        if (!StorageManager.isUserKeyUnlocked(userId)) {
-            // We always want to try to unlock the user key, even if the user is not started yet.
+        if (!StorageManager.isCeStorageUnlocked(userId)) {
+            // We always want to try to unlock CE storage, even if the user is not started yet.
             mLockPatternUtils.unlockUserKeyIfUnsecured(userId);
         }
         synchronized (mLock) {
@@ -2750,10 +2750,10 @@
                 case UserState.STATE_RUNNING_UNLOCKING:
                 case UserState.STATE_RUNNING_UNLOCKED:
                     return true;
-                // In the stopping/shutdown state return unlock state of the user key
+                // In the stopping/shutdown state, return unlock state of the user's CE storage.
                 case UserState.STATE_STOPPING:
                 case UserState.STATE_SHUTDOWN:
-                    return StorageManager.isUserKeyUnlocked(userId);
+                    return StorageManager.isCeStorageUnlocked(userId);
                 default:
                     return false;
             }
@@ -2762,10 +2762,10 @@
             switch (state.state) {
                 case UserState.STATE_RUNNING_UNLOCKED:
                     return true;
-                // In the stopping/shutdown state return unlock state of the user key
+                // In the stopping/shutdown state, return unlock state of the user's CE storage.
                 case UserState.STATE_STOPPING:
                 case UserState.STATE_SHUTDOWN:
-                    return StorageManager.isUserKeyUnlocked(userId);
+                    return StorageManager.isCeStorageUnlocked(userId);
                 default:
                     return false;
             }
@@ -3387,7 +3387,7 @@
 
     }
 
-    private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
+    BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
             @PowerWhitelistManager.ReasonCode int reasonCode) {
         long duration = 10_000;
         final ActivityManagerInternal amInternal =
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 26d99d8..cbaf05b 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "oomadjuster_correctness_rewrite"
-    namespace: "android_platform_power_optimization"
+    namespace: "backstage_power"
     description: "Utilize new OomAdjuster implementation"
     bug: "298055811"
     is_fixed_read_only: true
@@ -15,3 +15,10 @@
      description: "Feature flag for the ANR timer service"
      bug: "282428924"
 }
+
+flag {
+    name: "fgs_abuse_detection"
+    namespace: "backstage_power"
+    description: "Detect abusive FGS behavior for certain types (camera, mic, media, location)."
+    bug: "295545575"
+}
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ba43c8d..51cb950 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -165,7 +165,8 @@
 
     @Override
     public String toString() {
-        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
+        return "type: " + mDeviceType
+                + " internal type: 0x" + Integer.toHexString(mInternalDeviceType)
                 + " addr: " + mDeviceAddress + " bt audio type: "
                 + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
                 + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker
@@ -188,7 +189,7 @@
      * {@link AdiDeviceState#toPersistableString()}.
      */
     public static int getPeristedMaxSize() {
-        return 36;  /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+        return 36;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
                            + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
                            + (SETTINGS_FIELD_SEPARATOR)5 */
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9cfac9a..2336753 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -68,6 +68,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -496,8 +497,9 @@
             AudioDeviceInfo.TYPE_AUX_LINE
     };
 
-    /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
-        return isValidCommunicationDeviceType(device.getType());
+    /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(device, "device must not be null");
+        return device.isSink() && isValidCommunicationDeviceType(device.getType());
     }
 
     private static boolean isValidCommunicationDeviceType(int deviceType) {
@@ -1250,8 +1252,8 @@
     }
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
-            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1260,8 +1262,8 @@
     }
 
     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
-            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1279,8 +1281,8 @@
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
-            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
-        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1288,6 +1290,11 @@
         mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
     }
 
+    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+            List<AudioDeviceAttributes> devices) {
+        return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices);
+    }
+
     /*package*/ void registerCommunicationDeviceDispatcher(
             @NonNull ICommunicationDeviceDispatcher dispatcher) {
         mCommDevDispatchers.register(dispatcher);
@@ -2682,4 +2689,5 @@
     void clearDeviceInventory() {
         mDeviceInventory.clearDeviceInventory();
     }
+
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e08fdd6..d707689 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -121,6 +121,26 @@
     }
 
     /**
+     * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink
+     * Bluetooth device and no corresponding entry already exists.
+     * @param ada the device to add if needed
+     */
+    void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) {
+        if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) {
+            return;
+        }
+        synchronized (mDeviceInventoryLock) {
+            if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) {
+                return;
+            }
+            AdiDeviceState ads = new AdiDeviceState(
+                    ada.getType(), ada.getInternalType(), ada.getAddress());
+            mDeviceInventory.put(ads.getDeviceId(), ads);
+        }
+        mDeviceBroker.persistAudioDeviceSettings();
+    }
+
+    /**
      * Adds a new AdiDeviceState or updates the audio device cateogory of the matching
      * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
      * @param deviceState the device to update
@@ -904,9 +924,6 @@
             @NonNull List<AudioDeviceAttributes> devices) {
         int status = AudioSystem.ERROR;
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                    "setPreferredDevicesForStrategy, strategy: " + strategy
-                            + " devices: " + devices)).printLog(TAG));
             status = setDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */);
         }
@@ -932,10 +949,6 @@
         int status = AudioSystem.ERROR;
 
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "removePreferredDevicesForStrategy, strategy: "
-                            + strategy)).printLog(TAG));
-
             status = clearDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */);
         }
@@ -954,10 +967,6 @@
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
             List<AudioDeviceAttributes> devices = new ArrayList<>();
             devices.add(device);
-
-            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy
-                            + " device: " + device)).printLog(TAG));
             status = addDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
         }
@@ -975,11 +984,6 @@
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
             List<AudioDeviceAttributes> devices = new ArrayList<>();
             devices.add(device);
-
-            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "removeDeviceAsNonDefaultForStrategyAndSave, strategy: "
-                            + strategy + " devices: " + device)).printLog(TAG));
-
             status = removeDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
         }
@@ -992,8 +996,8 @@
 
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
-            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
-        mPrefDevDispatchers.register(dispatcher);
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mPrefDevDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1002,8 +1006,8 @@
     }
 
     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
-            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
-        mNonDefDevDispatchers.register(dispatcher);
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mNonDefDevDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1084,8 +1088,8 @@
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
-            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
-        mDevRoleCapturePresetDispatchers.register(dispatcher);
+            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+        mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1414,6 +1418,8 @@
                     updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
                     if (!connect) {
                         purgeDevicesRoles_l();
+                    } else {
+                        addAudioDeviceInInventoryIfNeeded(attributes);
                     }
                 }
                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1702,6 +1708,7 @@
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
 
         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+        addAudioDeviceInInventoryIfNeeded(ada);
     }
 
     static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2006,9 +2013,9 @@
         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
-
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_AVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
@@ -2018,6 +2025,7 @@
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
+        addAudioDeviceInInventoryIfNeeded(ada);
         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                 .set(MediaMetrics.Property.DEVICE,
@@ -2128,6 +2136,7 @@
                             sensorUuid));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
+            addAudioDeviceInInventoryIfNeeded(ada);
         }
 
         if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2462,6 +2471,9 @@
         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
+                if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
@@ -2475,6 +2487,9 @@
         final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
+                if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
@@ -2488,6 +2503,9 @@
         final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; ++i) {
             try {
+                if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
                         capturePreset, role, devices);
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 241abaf..85acf70 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -47,6 +47,12 @@
                 return setEncodedSurroundMode();
             case "get-encoded-surround-mode":
                 return getEncodedSurroundMode();
+            case "set-sound-dose-value":
+                return setSoundDoseValue();
+            case "get-sound-dose-value":
+                return getSoundDoseValue();
+            case "reset-sound-dose-timeout":
+                return resetSoundDoseTimeout();
         }
         return 0;
     }
@@ -66,6 +72,12 @@
         pw.println("    Sets the encoded surround sound mode to SURROUND_SOUND_MODE");
         pw.println("  get-encoded-surround-mode");
         pw.println("    Returns the encoded surround sound mode");
+        pw.println("  set-sound-dose-value");
+        pw.println("    Sets the current sound dose value");
+        pw.println("  get-sound-dose-value");
+        pw.println("    Returns the current sound dose value");
+        pw.println("  reset-sound-dose-timeout");
+        pw.println("    Resets the sound dose timeout used for momentary exposure");
     }
 
     private int setSurroundFormatEnabled() {
@@ -162,4 +174,46 @@
         getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode());
         return 0;
     }
+
+    private int setSoundDoseValue() {
+        String soundDoseValueText = getNextArg();
+
+        if (soundDoseValueText == null) {
+            getErrPrintWriter().println("Error: no sound dose value specified");
+            return 1;
+        }
+
+        float soundDoseValue = 0.f;
+        try {
+            soundDoseValue = Float.parseFloat(soundDoseValueText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for sound dose");
+            return 1;
+        }
+
+        if (soundDoseValue < 0) {
+            getErrPrintWriter().println("Error: invalid value of sound dose");
+            return 1;
+        }
+
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setCsd(soundDoseValue);
+        return 0;
+    }
+
+    private int getSoundDoseValue() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        getOutPrintWriter().println("Sound dose value: " + am.getCsd());
+        return 0;
+    }
+
+    private int resetSoundDoseTimeout() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setCsd(-1.f);
+        getOutPrintWriter().println("Reset sound dose momentary exposure timeout");
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0aa9cc1..a8fa313 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -16,8 +16,8 @@
 
 package com.android.server.audio;
 
-import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
+import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -73,7 +73,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -151,6 +150,7 @@
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.media.projection.IMediaProjectionManager;
+import android.media.session.MediaSessionManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -203,6 +203,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.media.audio.flags.Flags;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -267,6 +268,8 @@
     private final SettingsAdapter mSettings;
     private final AudioPolicyFacade mAudioPolicy;
 
+    private final MusicFxHelper mMusicFxHelper;
+
     /** Debug audio mode */
     protected static final boolean DEBUG_MODE = false;
 
@@ -307,6 +310,9 @@
     private final ContentResolver mContentResolver;
     private final AppOpsManager mAppOps;
 
+    /** do not use directly, use getMediaSessionManager() which handles lazy initialization */
+    @Nullable private volatile MediaSessionManager mMediaSessionManager;
+
     // the platform type affects volume and silent mode behavior
     private final int mPlatformType;
 
@@ -407,9 +413,15 @@
     private static final int MSG_CONFIGURATION_CHANGED = 54;
     private static final int MSG_BROADCAST_MASTER_MUTE = 55;
 
-    /** Messages handled by the {@link SoundDoseHelper}. */
+    /**
+     * Messages handled by the {@link SoundDoseHelper}, do not exceed
+     * {@link MUSICFX_HELPER_MSG_START}.
+     */
     /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
 
+    /** Messages handled by the {@link MusicFxHelper}. */
+    /*package*/ static final int MUSICFX_HELPER_MSG_START = 1100;
+
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -932,6 +944,8 @@
 
     private final SoundDoseHelper mSoundDoseHelper;
 
+    private final HardeningEnforcer mHardeningEnforcer;
+
     private final Object mSupportedSystemUsagesLock = new Object();
     @GuardedBy("mSupportedSystemUsagesLock")
     private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -1304,6 +1318,10 @@
                 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
 
         mDisplayManager = context.getSystemService(DisplayManager.class);
+
+        mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
+
+        mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive());
     }
 
     private void initVolumeStreamStates() {
@@ -1373,7 +1391,6 @@
 
         // check on volume initialization
         checkVolumeRangeInitialization("AudioService()");
-
     }
 
     private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1386,6 +1403,14 @@
                 }
             };
 
+    private MediaSessionManager getMediaSessionManager() {
+        if (mMediaSessionManager == null) {
+            mMediaSessionManager = (MediaSessionManager) mContext
+                    .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        }
+        return mMediaSessionManager;
+    }
+
     /**
      * Initialize intent receives and settings observers for this service.
      * Must be called after createStreamStates() as the handling of some events
@@ -2873,7 +2898,7 @@
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
     /**
-     * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
      *                                                  List<AudioDeviceAttributes>)
      */
@@ -2883,8 +2908,11 @@
         if (devices == null) {
             return AudioSystem.ERROR;
         }
+
+        devices = retrieveBluetoothAddresses(devices);
+
         final String logString = String.format(
-                "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
+                "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy,
                 devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2908,11 +2936,11 @@
         super.removePreferredDevicesForStrategy_enforcePermission();
 
         final String logString =
-                String.format("removePreferredDeviceForStrategy strat:%d", strategy);
+                String.format("removePreferredDevicesForStrategy strat:%d", strategy);
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
 
         final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
-        if (status != AudioSystem.SUCCESS) {
+        if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) {
             Log.e(TAG, String.format("Error %d in %s)", status, logString));
         }
         return status;
@@ -2940,7 +2968,7 @@
                     status, strategy));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -2955,6 +2983,9 @@
                                                 @NonNull AudioDeviceAttributes device) {
         super.setDeviceAsNonDefaultForStrategy_enforcePermission();
         Objects.requireNonNull(device);
+
+        device = retrieveBluetoothAddress(device);
+
         final String logString = String.format(
                 "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
@@ -2981,6 +3012,9 @@
                                                    AudioDeviceAttributes device) {
         super.removeDeviceAsNonDefaultForStrategy_enforcePermission();
         Objects.requireNonNull(device);
+
+        device = retrieveBluetoothAddress(device);
+
         final String logString = String.format(
                 "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString());
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2990,7 +3024,7 @@
         }
 
         final int status = mDeviceBroker.removeDeviceAsNonDefaultForStrategySync(strategy, device);
-        if (status != AudioSystem.SUCCESS) {
+        if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) {
             Log.e(TAG, String.format("Error %d in %s)", status, logString));
         }
         return status;
@@ -3015,7 +3049,7 @@
                     status, strategy));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -3028,7 +3062,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -3052,7 +3087,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
@@ -3068,7 +3104,7 @@
     }
 
     /**
-     * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
      */
     public int setPreferredDevicesForCapturePreset(
             int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -3087,6 +3123,8 @@
             return AudioSystem.ERROR;
         }
 
+        devices = retrieveBluetoothAddresses(devices);
+
         final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
                 capturePreset, devices);
         if (status != AudioSystem.SUCCESS) {
@@ -3106,7 +3144,7 @@
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
 
         final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset);
-        if (status != AudioSystem.SUCCESS) {
+        if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) {
             Log.e(TAG, String.format("Error %d in %s", status, logString));
         }
         return status;
@@ -3133,7 +3171,7 @@
                     status, capturePreset));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -3147,7 +3185,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /**
@@ -3167,7 +3206,9 @@
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
         enforceQueryStateOrModifyRoutingPermission();
-        return getDevicesForAttributesInt(attributes, false /* forVolume */);
+
+        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+                getDevicesForAttributesInt(attributes, false /* forVolume */)));
     }
 
     /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes)
@@ -3177,7 +3218,8 @@
      */
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
             @NonNull AudioAttributes attributes) {
-        return getDevicesForAttributesInt(attributes, false /* forVolume */);
+        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+                getDevicesForAttributesInt(attributes, false /* forVolume */)));
     }
 
     /**
@@ -3397,6 +3439,10 @@
      * Part of service interface, check permissions here */
     public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags,
             String callingPackage, String attributionTag) {
+        if (mHardeningEnforcer.blockVolumeMethod(
+                HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) {
+            return;
+        }
         if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
             Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
                     + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
@@ -4173,6 +4219,10 @@
      * Part of service interface, check permissions here */
     public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
             String callingPackage, String attributionTag) {
+        if (mHardeningEnforcer.blockVolumeMethod(
+                HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) {
+            return;
+        }
         setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
                 callingPackage, attributionTag);
     }
@@ -5025,6 +5075,7 @@
     /** @see AudioManager#setMasterMute(boolean, int) */
     public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
             String attributionTag) {
+
         super.setMasterMute_enforcePermission();
 
         setMasterMuteInternal(mute, flags, callingPackage,
@@ -5390,6 +5441,10 @@
     }
 
     public void setRingerModeExternal(int ringerMode, String caller) {
+        if (mHardeningEnforcer.blockVolumeMethod(
+                HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) {
+            return;
+        }
         if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode)
                 && !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) {
             throw new SecurityException("Not allowed to change Do Not Disturb state");
@@ -6142,6 +6197,35 @@
                 AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
     }
 
+    /**
+      * @see AudioManager#adjustVolume(int, int)
+      * This method is redirected from AudioManager to AudioService for API hardening rules
+      * enforcement then to MediaSession for implementation.
+      */
+    @Override
+    public void adjustVolume(int direction, int flags) {
+        if (mHardeningEnforcer.blockVolumeMethod(
+                HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) {
+            return;
+        }
+        getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
+                    direction, flags);
+    }
+
+    /**
+     * @see AudioManager#adjustSuggestedStreamVolume(int, int, int)
+     * This method is redirected from AudioManager to AudioService for API hardening rules
+     * enforcement then to MediaSession for implementation.
+     */
+    @Override
+    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
+        if (mHardeningEnforcer.blockVolumeMethod(
+                HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) {
+            return;
+        }
+        getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags);
+    }
+
     /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */
     @Override
     public void setStreamVolumeForUid(int streamType, int index, int flags,
@@ -7382,6 +7466,8 @@
         Objects.requireNonNull(device);
         AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
 
+        device = retrieveBluetoothAddress(device);
+
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
                 + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
                 + device.getAddress() + " behavior:"
@@ -7465,6 +7551,8 @@
         // verify parameters
         Objects.requireNonNull(device);
 
+        device = retrieveBluetoothAddress(device);
+
         return getDeviceVolumeBehaviorInt(device);
     }
 
@@ -7539,9 +7627,12 @@
     /**
      * see AudioManager.setWiredDeviceConnectionState()
      */
-    public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+    public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes,
             @ConnectionState int state, String caller) {
         super.setWiredDeviceConnectionState_enforcePermission();
+        Objects.requireNonNull(attributes);
+
+        attributes = retrieveBluetoothAddress(attributes);
 
         if (state != CONNECTION_STATE_CONNECTED
                 && state != CONNECTION_STATE_DISCONNECTED) {
@@ -7582,6 +7673,9 @@
             boolean connected) {
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        device = retrieveBluetoothAddress(device);
+
         mDeviceBroker.setTestDeviceConnectionState(device,
                 connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
         // simulate a routing update from native
@@ -9456,11 +9550,21 @@
                     onConfigurationChanged();
                     break;
 
+                case MusicFxHelper.MSG_EFFECT_CLIENT_GONE:
+                    mMusicFxHelper.handleMessage(msg);
+                    break;
+
+                case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA:
+                case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA_FORCED:
+                case SoundDoseHelper.MSG_PERSIST_SAFE_VOLUME_STATE:
+                case SoundDoseHelper.MSG_PERSIST_MUSIC_ACTIVE_MS:
+                case SoundDoseHelper.MSG_PERSIST_CSD_VALUES:
+                case SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION:
+                    mSoundDoseHelper.handleMessage(msg);
+                    break;
+
                 default:
-                    if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
-                        // msg could be for the SoundDoseHelper
-                        mSoundDoseHelper.handleMessage(msg);
-                    }
+                    Log.e(TAG, "Unsupported msgId " + msg.what);
             }
         }
     }
@@ -9695,7 +9799,7 @@
                 }
             } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
                     action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
-                handleAudioEffectBroadcast(context, intent);
+                mMusicFxHelper.handleAudioEffectBroadcast(context, intent);
             } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 final int[] suspendedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                 final String[] suspendedPackages =
@@ -9750,27 +9854,6 @@
         }
     } // end class AudioServiceUserRestrictionsListener
 
-    private void handleAudioEffectBroadcast(Context context, Intent intent) {
-        String target = intent.getPackage();
-        if (target != null) {
-            Log.w(TAG, "effect broadcast already targeted to " + target);
-            return;
-        }
-        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
-        // TODO this should target a user-selected panel
-        List<ResolveInfo> ril = context.getPackageManager().queryBroadcastReceivers(
-                intent, 0 /* flags */);
-        if (ril != null && ril.size() != 0) {
-            ResolveInfo ri = ril.get(0);
-            if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
-                intent.setPackage(ri.activityInfo.packageName);
-                context.sendBroadcastAsUser(intent, UserHandle.ALL);
-                return;
-            }
-        }
-        Log.w(TAG, "couldn't find receiver package for effect intent");
-    }
-
     private void killBackgroundUserProcessesWithRecordAudioPermission(UserInfo oldUser) {
         PackageManager pm = mContext.getPackageManager();
         // Find the home activity of the user. It should not be killed to avoid expensive restart,
@@ -10425,6 +10508,103 @@
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
+    private boolean isBluetoothPrividged() {
+        if (!Flags.bluetoothMacAddressAnonymization()) {
+            return true;
+        }
+        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH_CONNECT)
+                || Binder.getCallingUid() == Process.SYSTEM_UID;
+    }
+
+    List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) {
+        if (isBluetoothPrividged()) {
+            return devices;
+        }
+
+        List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>();
+        for (AudioDeviceAttributes ada : devices) {
+            if (ada == null) {
+                continue;
+            }
+            checkedDevices.add(retrieveBluetoothAddressUncheked(ada));
+        }
+        return checkedDevices;
+    }
+
+    AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) {
+        if (isBluetoothPrividged()) {
+            return ada;
+        }
+        return retrieveBluetoothAddressUncheked(ada);
+    }
+
+    AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) {
+        Objects.requireNonNull(ada);
+        if (AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+            String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress());
+            for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
+                if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
+                        && (ada.getInternalType() == ads.getInternalDeviceType())
+                        && anonymizedAddress.equals(anonymizeBluetoothAddress(
+                                ads.getDeviceAddress())))) {
+                    continue;
+                }
+                ada.setAddress(ads.getDeviceAddress());
+                break;
+            }
+        }
+        return ada;
+    }
+
+    /**
+     * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
+     * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
+     * @param address Mac address to be anonymized
+     * @return anonymized mac address
+     */
+    static String anonymizeBluetoothAddress(String address) {
+        if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) {
+            return null;
+        }
+        return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
+    }
+
+    private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList(
+                List<AudioDeviceAttributes> devices) {
+        if (isBluetoothPrividged()) {
+            return devices;
+        }
+        return anonymizeAudioDeviceAttributesListUnchecked(devices);
+    }
+
+    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+            List<AudioDeviceAttributes> devices) {
+        List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>();
+        for (AudioDeviceAttributes ada : devices) {
+            anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada));
+        }
+        return anonymizedDevices;
+    }
+
+    private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked(
+            AudioDeviceAttributes ada) {
+        if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+            return ada;
+        }
+        AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
+        res.setAddress(anonymizeBluetoothAddress(ada.getAddress()));
+        return res;
+    }
+
+    private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) {
+        if (isBluetoothPrividged()) {
+            return ada;
+        }
+
+        return anonymizeAudioDeviceAttributesUnchecked(ada);
+    }
+
     //==========================================================================================
 
     // camera sound is forced if any of the resources corresponding to one active SIM
@@ -10472,13 +10652,16 @@
         Objects.requireNonNull(usages);
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
         if (timeOutMs <= 0 || usages.length == 0) {
             throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
         }
         Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs
                 + " usages:" + Arrays.toString(usages));
 
-        if (mDeviceBroker.isDeviceConnected(device)) {
+        if (mDeviceBroker.isDeviceConnected(ada)) {
             // not throwing an exception as there could be a race between a connection (server-side,
             // notification of connection in flight) and a mute operation (client-side)
             Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
@@ -10490,12 +10673,19 @@
                         + mMutingExpectedDevice);
                 throw new IllegalStateException("muteAwaitConnection already in progress");
             }
-            mMutingExpectedDevice = device;
+            mMutingExpectedDevice = ada;
             mMutedUsagesAwaitingConnection = usages;
-            mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+            mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs);
         }
-        dispatchMuteAwaitConnection(cb -> { try {
-            cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes dev = ada;
+                if (!isPrivileged) {
+                    dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+                }
+                cb.dispatchOnMutedUntilConnection(dev, usages);
+            } catch (RemoteException e) { }
+        });
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -10504,7 +10694,7 @@
         super.getMutingExpectedDevice_enforcePermission();
 
         synchronized (mMuteAwaitConnectionLock) {
-            return mMutingExpectedDevice;
+            return anonymizeAudioDeviceAttributes(mMutingExpectedDevice);
         }
     }
 
@@ -10513,6 +10703,9 @@
     public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
         Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
         final int[] mutedUsages;
         synchronized (mMuteAwaitConnectionLock) {
@@ -10522,7 +10715,7 @@
                 Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
                 return;
             }
-            if (!device.equalTypeAddress(mMutingExpectedDevice)) {
+            if (!ada.equalTypeAddress(mMutingExpectedDevice)) {
                 Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
                         + "] but expected device is" + mMutingExpectedDevice);
                 throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
@@ -10532,8 +10725,14 @@
             mMutedUsagesAwaitingConnection = null;
             mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device);
         }
-        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
-                    AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes dev = ada;
+                if (!isPrivileged) {
+                    dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+                }
+                cb.dispatchOnUnmutedEvent(
+                        AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages);
             } catch (RemoteException e) { } });
     }
 
@@ -10547,7 +10746,7 @@
         super.registerMuteAwaitConnectionDispatcher_enforcePermission();
 
         if (register) {
-            mMuteAwaitConnectionDispatchers.register(cb);
+            mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged());
         } else {
             mMuteAwaitConnectionDispatchers.unregister(cb);
         }
@@ -10571,8 +10770,14 @@
             mPlaybackMonitor.cancelMuteAwaitConnection(
                     "checkMuteAwaitConnection device " + device + " connected, unmuting");
         }
-        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
-                AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes ada = device;
+                if (!isPrivileged) {
+                    ada = anonymizeAudioDeviceAttributesUnchecked(device);
+                }
+                cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION,
+                        ada, mutedUsages);
             } catch (RemoteException e) { } });
     }
 
@@ -10592,7 +10797,8 @@
             mMutingExpectedDevice = null;
             mMutedUsagesAwaitingConnection = null;
         }
-        dispatchMuteAwaitConnection(cb -> { try {
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
                 cb.dispatchOnUnmutedEvent(
                         AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
                         timedOutDevice, mutedUsages);
@@ -10600,13 +10806,14 @@
     }
 
     private void dispatchMuteAwaitConnection(
-            java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+            java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) {
         final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
         // lazy initialization as errors unlikely
         ArrayList<IMuteAwaitConnectionCallback> errorList = null;
         for (int i = 0; i < nbDispatchers; i++) {
             try {
-                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i),
+                        (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i));
             } catch (Exception e) {
                 if (errorList == null) {
                     errorList = new ArrayList<>(1);
@@ -13246,6 +13453,9 @@
             @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) {
         Objects.requireNonNull(device, "device must not be null");
         enforceModifyAudioRoutingPermission();
+
+        device = retrieveBluetoothAddress(device);
+
         final String getterKey = "additional_output_device_delay="
                 + device.getInternalType() + "," + device.getAddress(); // "getter" key as an id.
         final String setterKey = getterKey + "," + delayMillis;     // append the delay for setter
@@ -13266,6 +13476,9 @@
     @IntRange(from = 0)
     public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device, "device must not be null");
+
+        device = retrieveBluetoothAddress(device);
+
         final String key = "additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
@@ -13293,6 +13506,9 @@
     @IntRange(from = 0)
     public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device, "device must not be null");
+
+        device = retrieveBluetoothAddress(device);
+
         final String key = "max_additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 6af409e..7d7e6d0 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -44,7 +44,9 @@
 
 import java.io.PrintWriter;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -66,6 +68,8 @@
 
     // Bluetooth headset device
     private @Nullable BluetoothDevice mBluetoothHeadsetDevice;
+    private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices =
+            new HashMap<>();
 
     private @Nullable BluetoothHearingAid mHearingAid;
 
@@ -590,7 +594,16 @@
         if (mBluetoothHeadsetDevice == null) {
             return null;
         }
-        return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice);
+        return getHeadsetAudioDevice(mBluetoothHeadsetDevice);
+    }
+
+    private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) {
+        AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice);
+        if (deviceAttr != null) {
+            // Returns the cached device attributes so that it is consistent as the previous one.
+            return deviceAttr;
+        }
+        return btHeadsetDeviceToAudioDevice(btDevice);
     }
 
     private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
@@ -628,7 +641,7 @@
             return true;
         }
         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
-        AudioDeviceAttributes audioDevice =  btHeadsetDeviceToAudioDevice(btDevice);
+        AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
         boolean result = false;
         if (isActive) {
             result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
@@ -648,6 +661,13 @@
         result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
                         inDevice, audioDevice.getAddress(), audioDevice.getName()),
                 isActive, btDevice) && result;
+        if (result) {
+            if (isActive) {
+                mResolvedScoAudioDevices.put(btDevice, audioDevice);
+            } else {
+                mResolvedScoAudioDevices.remove(btDevice);
+            }
+        }
         return result;
     }
 
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
new file mode 100644
index 0000000..c7556da
--- /dev/null
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Class to encapsulate all audio API hardening operations
+ */
+public class HardeningEnforcer {
+
+    private static final String TAG = "AS.HardeningEnforcer";
+
+    final Context mContext;
+    final boolean mIsAutomotive;
+
+    /**
+     * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
+     */
+    public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100;
+    /**
+     * Matches calls from {@link AudioManager#adjustVolume(int, int)}
+     */
+    public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101;
+    /**
+     * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)}
+     */
+    public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102;
+    /**
+     * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)}
+     */
+    public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103;
+    /**
+     * Matches calls from {@link AudioManager#setRingerMode(int)}
+     */
+    public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200;
+
+    public HardeningEnforcer(Context ctxt, boolean isAutomotive) {
+        mContext = ctxt;
+        mIsAutomotive = isAutomotive;
+    }
+
+    /**
+     * Checks whether the call in the current thread should be allowed or blocked
+     * @param volumeMethod name of the method to check, for logging purposes
+     * @return false if the method call is allowed, true if it should be a no-op
+     */
+    protected boolean blockVolumeMethod(int volumeMethod) {
+        // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED
+        if (mIsAutomotive) {
+            if (!autoPublicVolumeApiHardening()) {
+                // automotive hardening flag disabled, no blocking on auto
+                return false;
+            }
+            if (mContext.checkCallingOrSelfPermission(
+                    Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+            if (Binder.getCallingUid() < UserHandle.AID_APP_START) {
+                return false;
+            }
+            // TODO metrics?
+            // TODO log for audio dumpsys?
+            Log.e(TAG, "Preventing volume method " + volumeMethod + " for "
+                    + getPackNameForUid(Binder.getCallingUid()));
+            return true;
+        }
+        // not blocking
+        return false;
+    }
+
+    private String getPackNameForUid(int uid) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final String[] names = mContext.getPackageManager().getPackagesForUid(uid);
+            if (names == null
+                    || names.length == 0
+                    || TextUtils.isEmpty(names[0])) {
+                return "[" + uid + "]";
+            }
+            return names[0];
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
new file mode 100644
index 0000000..5f4e4c3
--- /dev/null
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.IUidObserver;
+import android.app.UidObserver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.AudioManager;
+import android.media.audiofx.AudioEffect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.AudioHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MusicFx management.
+ * .
+ */
+public class MusicFxHelper {
+    private static final String TAG = "AS.MusicFxHelper";
+
+    @NonNull private final Context mContext;
+
+    @NonNull private final AudioHandler mAudioHandler;
+
+    // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver.
+    private final Object mClientUidMapLock = new Object();
+
+    // The binder token identifying the UidObserver registration.
+    private IBinder mUidObserverToken = null;
+
+    // Hashmap of UID and list of open sessions for this UID.
+    @GuardedBy("mClientUidMapLock")
+    private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>();
+
+    /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
+
+    // UID observer for effect MusicFx clients
+    private final IUidObserver mEffectUidObserver = new UidObserver() {
+        @Override public void onUidGone(int uid, boolean disabled) {
+            Log.w(TAG, " send MSG_EFFECT_CLIENT_GONE");
+            mAudioHandler.sendMessageAtTime(
+                    mAudioHandler.obtainMessage(MSG_EFFECT_CLIENT_GONE,
+                            uid /* arg1 */, 0 /* arg2 */,
+                            null /* obj */), 0 /* delay */);
+        }
+    };
+
+    // BindService connection implementation, we don't need any implementation now
+    private ServiceConnection mMusicFxBindConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, " service connected to " + name);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, " service disconnected from " + name);
+        }
+    };
+
+    MusicFxHelper(@NonNull Context context, @NonNull AudioHandler audioHandler) {
+        mContext = context;
+        mAudioHandler = audioHandler;
+    }
+
+    /**
+     * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
+     * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
+     *
+     * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}:
+     *  - If the MusicFx process is not running, call bindService with AUTO_CREATE to create.
+     *  - If this is the first audio session in MusicFx, call set foreground service delegate.
+     *  - If this is the first audio session for a given UID, add the UID into observer.
+     *
+     * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}:
+     *  - MusicFx will not be foreground delegated anymore.
+     *  - The KeepAliveService of MusicFx will be unbound.
+     *  - The UidObserver will be removed.
+     */
+    public void handleAudioEffectBroadcast(Context context, Intent intent) {
+        String target = intent.getPackage();
+        if (target != null) {
+            Log.w(TAG, "effect broadcast already targeted to " + target);
+            return;
+        }
+        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        final PackageManager pm = context.getPackageManager();
+        // TODO this should target a user-selected panel
+        List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */);
+        if (ril != null && ril.size() != 0) {
+            ResolveInfo ri = ril.get(0);
+            final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
+            try {
+                final int senderUid = pm.getPackageUidAsUser(senderPackageName,
+                        PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+                if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
+                    intent.setPackage(ri.activityInfo.packageName);
+                    synchronized (mClientUidMapLock) {
+                        setMusicFxServiceWithObserver(context, intent, senderUid);
+                    }
+                    context.sendBroadcastAsUser(intent, UserHandle.ALL);
+                    return;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Not able to find UID from package: " + senderPackageName + " error: "
+                        + e);
+            }
+        }
+        Log.w(TAG, "couldn't find receiver package for effect intent");
+    }
+
+    /**
+     * Handle the UidObserver onUidGone callback of MusicFx clients.
+     * All open audio sessions of this UID will be closed.
+     * If this is the last UID for MusicFx:
+     *  - MusicFx will not be foreground delegated anymore.
+     *  - The KeepAliveService of MusicFx will be unbound.
+     *  - The UidObserver will be removed.
+     */
+    public void handleEffectClientUidGone(int uid) {
+        synchronized (mClientUidMapLock) {
+            Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
+            // Once the uid is no longer running, close all remain audio session(s) for this UID
+            if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
+                final List<Integer> sessions =
+                        new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
+                Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
+                for (Integer session : sessions) {
+                    Intent intent = new Intent(
+                            AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+                    intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session);
+                    setMusicFxServiceWithObserver(mContext, intent, uid);
+                    Log.i(TAG, "Close session " + session + " of UID " + uid);
+                }
+                mClientUidSessionMap.remove(Integer.valueOf(uid));
+            }
+        }
+    }
+
+    @GuardedBy("mClientUidMapLock")
+    private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) {
+        PackageManager pm = context.getPackageManager();
+        try {
+            final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
+                    AudioManager.AUDIO_SESSION_ID_GENERATE);
+            if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) {
+                Log.e(TAG, "Intent missing audio session: " + audioSession);
+                return;
+            }
+
+            // only apply to com.android.musicfx and KeepAliveService for now
+            final String musicFxPackageName = "com.android.musicfx";
+            final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService";
+            final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName,
+                    PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+
+            if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
+                List<Integer> sessions = new ArrayList<>();
+                Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession);
+                // start foreground service delegate and register UID observer with the first
+                // session of first UID open
+                if (0 == mClientUidSessionMap.size()) {
+                    final int procState = ActivityManager.getService().getPackageProcessState(
+                            musicFxPackageName, this.getClass().getPackage().getName());
+                    // if musicfx process not in binding state, call bindService with AUTO_CREATE
+                    if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                        Intent bindIntent = new Intent().setClassName(musicFxPackageName,
+                                musicFxKeepAliveService);
+                        context.bindServiceAsUser(
+                                bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
+                                UserHandle.of(getCurrentUserId()));
+                        Log.i(TAG, "bindService to " + musicFxPackageName);
+                    }
+
+                    Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid
+                            + " procState " + procState);
+                } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) {
+                    sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+                    if (sessions.contains(audioSession)) {
+                        Log.e(TAG, "Audio session " + audioSession + " already exist for UID "
+                                + senderUid + ", abort");
+                        return;
+                    }
+                }
+                // first session of this UID
+                if (sessions.size() == 0) {
+                    // call registerUidObserverForUids with the first UID and first session
+                    if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) {
+                        mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
+                                mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
+                                ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid});
+                        Log.i(TAG, "UID " + senderUid + " registered to observer");
+                    } else {
+                        // add UID to observer for each new UID
+                        ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG,
+                                senderUid);
+                        Log.i(TAG, "UID " + senderUid + " addeded to observer");
+                    }
+                }
+
+                sessions.add(Integer.valueOf(audioSession));
+                mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
+            } else {
+                if (mClientUidSessionMap.get(senderUid) != null) {
+                    Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession);
+                    List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+                    sessions.remove(Integer.valueOf(audioSession));
+                    if (0 == sessions.size()) {
+                        mClientUidSessionMap.remove(Integer.valueOf(senderUid));
+                    } else {
+                        mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
+                    }
+
+                    // stop foreground service delegate and unregister UID observer with the
+                    // last session of last UID close
+                    if (0 == mClientUidSessionMap.size()) {
+                        ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
+                        mClientUidSessionMap.clear();
+                        context.unbindService(mMusicFxBindConnection);
+                        Log.i(TAG, " remove all sessions, unregister UID observer, and unbind "
+                                + musicFxPackageName);
+                    }
+                } else {
+                    // if the audio session already closed, print an error
+                    Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession
+                            + " which does not exist");
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Not able to find UID from package: " + e);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException " + e + " with handling intent");
+        }
+    }
+
+    private int getCurrentUserId() {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            UserInfo currentUser = ActivityManager.getService().getCurrentUser();
+            return currentUser.id;
+        } catch (RemoteException e) {
+            // Activity manager not running, nothing we can do assume user 0.
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return UserHandle.USER_SYSTEM;
+    }
+
+    /*package*/ void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_EFFECT_CLIENT_GONE:
+                Log.w(TAG, " handle MSG_EFFECT_CLIENT_GONE");
+                handleEffectClientUidGone(msg.arg1 /* uid */);
+                break;
+            default:
+                Log.e(TAG, "Unexpected msg to handle in MusicFxHelper: " + msg.what);
+                break;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 81365bf..d65c7c2c 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -108,11 +108,11 @@
     private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
     private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
 
-    private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
-    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
+    /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
+    /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
+    /*package*/ static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
+    /*package*/ static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
+    /*package*/ static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
     /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6;
 
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
@@ -189,6 +189,8 @@
 
     private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
 
+    private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false);
+
     private final Object mCsdAsAFeatureLock = new Object();
 
     @GuardedBy("mCsdAsAFeatureLock")
@@ -375,9 +377,21 @@
         }
     }
 
+    private boolean updateCsdForTestApi() {
+        if (mForceCsdProperty.get() != SystemProperties.getBoolean(
+                SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) {
+            updateCsdEnabled("SystemPropertiesChangeCallback");
+        }
+
+        return mEnableCsd.get();
+    }
+
     float getCsd() {
         if (!mEnableCsd.get()) {
-            return -1.f;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return -1.f;
+            }
         }
 
         final ISoundDose soundDose = mSoundDose.get();
@@ -396,7 +410,10 @@
 
     void setCsd(float csd) {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         SoundDoseRecord[] doseRecordsArray;
@@ -430,7 +447,10 @@
 
     void resetCsdTimeouts() {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         synchronized (mCsdStateLock) {
@@ -440,7 +460,10 @@
 
     void forceUseFrameworkMel(boolean useFrameworkMel) {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         final ISoundDose soundDose = mSoundDose.get();
@@ -458,7 +481,10 @@
 
     void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         final ISoundDose soundDose = mSoundDose.get();
@@ -488,7 +514,7 @@
         try {
             return soundDose.isSoundDoseHalSupported();
         } catch (RemoteException e) {
-            Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
+            Log.e(TAG, "Exception while querying the csd enabled status", e);
         }
         return false;
     }
@@ -544,7 +570,7 @@
             audioDeviceCategory.csdCompatible = isHeadphone;
             soundDose.setAudioDeviceCategory(audioDeviceCategory);
         } catch (RemoteException e) {
-            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+            Log.e(TAG, "Exception while setting the audio device category", e);
         }
     }
 
@@ -894,7 +920,7 @@
                 mCachedAudioDeviceCategories.clear();
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+            Log.e(TAG, "Exception while initializing the cached audio device categories", e);
         }
 
         synchronized (mCsdAsAFeatureLock) {
@@ -991,19 +1017,20 @@
     }
 
     private void updateCsdEnabled(String caller) {
-        boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false);
+        mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
+                false));
         // we are using the MCC overlaid legacy flag used for the safe volume enablement
         // to determine whether the MCC enforces any safe hearing standard.
         boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_safe_media_volume_enabled);
         boolean csdEnable = mContext.getResources().getBoolean(
                 R.bool.config_safe_sound_dosage_enabled);
-        boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce;
+        boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get();
 
         synchronized (mCsdAsAFeatureLock) {
             if (!mccEnforcedSafeMedia && csdEnable) {
                 mIsCsdAsAFeatureAvailable = true;
-                newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce;
+                newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get();
                 Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: "
                         + newEnabledCsd);
             } else {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 35260ed..7abd9c7 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -39,10 +39,9 @@
 import android.media.ISpatializerHeadTrackingModeCallback;
 import android.media.ISpatializerOutputCallback;
 import android.media.MediaMetrics;
-import android.media.SpatializationLevel;
-import android.media.SpatializationMode;
 import android.media.Spatializer;
-import android.media.SpatializerHeadTrackingMode;
+import android.media.audio.common.HeadTracking;
+import android.media.audio.common.Spatialization;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -84,22 +83,22 @@
 
     /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
         {
-            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
             // assumption for A2DP: mostly headsets
-            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL);
             // assumption that BLE broadcast would be mostly consumed on headsets
-            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL);
         }
     };
 
@@ -226,12 +225,12 @@
                 ArrayList<Integer> list = new ArrayList<>(0);
                 for (byte value : values) {
                     switch (value) {
-                        case SpatializerHeadTrackingMode.OTHER:
-                        case SpatializerHeadTrackingMode.DISABLED:
+                        case HeadTracking.Mode.OTHER:
+                        case HeadTracking.Mode.DISABLED:
                             // not expected here, skip
                             break;
-                        case SpatializerHeadTrackingMode.RELATIVE_WORLD:
-                        case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+                        case HeadTracking.Mode.RELATIVE_WORLD:
+                        case HeadTracking.Mode.RELATIVE_SCREEN:
                             list.add(headTrackingModeTypeToSpatializerInt(value));
                             break;
                         default:
@@ -254,10 +253,10 @@
             byte[] spatModes = spat.getSupportedModes();
             for (byte mode : spatModes) {
                 switch (mode) {
-                    case SpatializationMode.SPATIALIZER_BINAURAL:
+                    case Spatialization.Mode.BINAURAL:
                         mBinauralSupported = true;
                         break;
-                    case SpatializationMode.SPATIALIZER_TRANSAURAL:
+                    case Spatialization.Mode.TRANSAURAL:
                         mTransauralSupported = true;
                         break;
                     default:
@@ -274,8 +273,8 @@
             // initialize list of compatible devices
             for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) {
                 int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i);
-                if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                        || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL
+                if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported)
+                        || (mode == (int) Spatialization.Mode.TRANSAURAL
                             && mTransauralSupported)) {
                     mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
                 }
@@ -577,9 +576,9 @@
 
         int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
                 Integer.MIN_VALUE);
-        device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+        device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL
                 ? mBinauralEnabledDefault
-                : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                : spatMode == Spatialization.Mode.TRANSAURAL
                         ? mTransauralEnabledDefault
                         : false);
         device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
@@ -629,9 +628,9 @@
         if (isBluetoothDevice(internalDeviceType)) return deviceType;
 
         final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
-        if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+        if (spatMode == Spatialization.Mode.TRANSAURAL) {
             return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
-        } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+        } else if (spatMode == Spatialization.Mode.BINAURAL) {
             return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
         }
         return AudioDeviceInfo.TYPE_UNKNOWN;
@@ -690,8 +689,7 @@
             // since their physical characteristics are unknown
             if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN
                     || deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) {
-                available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL)
-                        && mBinauralSupported;
+                available = (spatMode == Spatialization.Mode.BINAURAL) && mBinauralSupported;
             } else {
                 available = false;
             }
@@ -804,8 +802,8 @@
         // not be included.
         final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
                 /*default when type not found*/ -1);
-        if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
+        if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported)
+                || (modeForDevice == Spatialization.Mode.TRANSAURAL
                         && mTransauralSupported)) {
             return true;
         }
@@ -1479,7 +1477,7 @@
     }
 
     synchronized void onInitSensors() {
-        final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
+        final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE);
         final String action = init ? "initializing" : "releasing";
         if (mSpat == null) {
             logloge("not " + action + " sensors, null spatializer");
@@ -1545,13 +1543,13 @@
     // SDK <-> AIDL converters
     private static int headTrackingModeTypeToSpatializerInt(byte mode) {
         switch (mode) {
-            case SpatializerHeadTrackingMode.OTHER:
+            case HeadTracking.Mode.OTHER:
                 return Spatializer.HEAD_TRACKING_MODE_OTHER;
-            case SpatializerHeadTrackingMode.DISABLED:
+            case HeadTracking.Mode.DISABLED:
                 return Spatializer.HEAD_TRACKING_MODE_DISABLED;
-            case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+            case HeadTracking.Mode.RELATIVE_WORLD:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
-            case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+            case HeadTracking.Mode.RELATIVE_SCREEN:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
@@ -1561,13 +1559,13 @@
     private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
         switch (sdkMode) {
             case Spatializer.HEAD_TRACKING_MODE_OTHER:
-                return SpatializerHeadTrackingMode.OTHER;
+                return HeadTracking.Mode.OTHER;
             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
-                return SpatializerHeadTrackingMode.DISABLED;
+                return HeadTracking.Mode.DISABLED;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
-                return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+                return HeadTracking.Mode.RELATIVE_WORLD;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
-                return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+                return HeadTracking.Mode.RELATIVE_SCREEN;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
         }
@@ -1575,11 +1573,11 @@
 
     private static int spatializationLevelToSpatializerInt(byte level) {
         switch (level) {
-            case SpatializationLevel.NONE:
+            case Spatialization.Level.NONE:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
-            case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+            case Spatialization.Level.MULTICHANNEL:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
-            case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+            case Spatialization.Level.BED_PLUS_OBJECTS:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
             default:
                 throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b537e0e..b12d831 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -6,3 +6,10 @@
   description: "This flag controls tunscany virtual HAL feature"
   bug: "294254230"
 }
+
+flag {
+    name: "de_hidl"
+    namespace: "biometrics_framework"
+    description: "feature flag for biometrics de-hidl"
+    bug: "287332354"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 78c95ad..a47135f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,6 +35,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+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;
@@ -116,7 +117,24 @@
 
     @LockoutTracker.LockoutMode
     public int handleFailedAttempt(int userId) {
-        return LockoutTracker.LOCKOUT_NONE;
+        if (Flags.deHidl()) {
+            if (mLockoutTracker != null) {
+                mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
+            }
+            @LockoutTracker.LockoutMode final int lockoutMode =
+                    getLockoutTracker().getLockoutModeForUser(userId);
+            final PerformanceTracker performanceTracker =
+                    PerformanceTracker.getInstanceForSensorId(getSensorId());
+            if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+                performanceTracker.incrementPermanentLockoutForUser(userId);
+            } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+                performanceTracker.incrementTimedLockoutForUser(userId);
+            }
+
+            return lockoutMode;
+        } else {
+            return LockoutTracker.LOCKOUT_NONE;
+        }
     }
 
     protected long getStartTimeMs() {
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 8a54ae5..037ea38a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -586,4 +586,13 @@
             }
         }, 10000);
     }
+
+    /**
+     * Handle stop user client when user switching occurs.
+     */
+    public void onUserStopped() {}
+
+    public Handler getHandler() {
+        return mHandler;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
index 95c4903..35e9bcb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
@@ -32,6 +32,7 @@
         mUserLockoutStates = new SparseIntArray();
     }
 
+    @Override
     public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
         Slog.d(TAG, "Lockout for user: " + userId +  " is " + mode);
         synchronized (this) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
index 4a59c9d..8271380 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
@@ -35,4 +35,7 @@
     @interface LockoutMode {}
 
     @LockoutMode int getLockoutModeForUser(int userId);
+    void setLockoutModeForUser(int userId, @LockoutMode int mode);
+    default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {}
+    default void addFailedAttemptForUser(int userId) {}
 }
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 694dfd2..8075470 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -165,6 +165,7 @@
         }
     }
 
+    @Override
     public void onUserStopped() {
         if (mStopUserClient == null) {
             Slog.e(getTag(), "Unexpected onUserStopped");
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index cc00227..e5d4a63 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -31,6 +31,11 @@
         return mCurrentUserLockoutMode;
     }
 
+    @Override
+    public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+        setCurrentUserLockoutMode(mode);
+    }
+
     public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) {
         mCurrentUserLockoutMode = lockoutMode;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
index 573c20f..d149f52 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
@@ -38,7 +38,7 @@
 /**
  * Utilities for converting from hardware to framework-defined AIDL models.
  */
-final class AidlConversionUtils {
+public final class AidlConversionUtils {
 
     private static final String TAG = "AidlConversionUtils";
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
new file mode 100644
index 0000000..57f5b34
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.AuthenticationFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.hardware.face.Face;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+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.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface HardwareUnavailableCallback {
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
+    private static final String TAG = "AidlResponseHandler";
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricScheduler mScheduler;
+    private final int mSensorId;
+    private final int mUserId;
+    @NonNull
+    private final LockoutTracker mLockoutCache;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+    @NonNull
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutTracker lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        mContext = context;
+        mScheduler = scheduler;
+        mSensorId = sensorId;
+        mUserId = userId;
+        mLockoutCache = lockoutTracker;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mHardwareUnavailableCallback = hardwareUnavailableCallback;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return this.HASH;
+    }
+
+    @Override
+    public void onChallengeGenerated(long challenge) {
+        handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
+                mUserId, challenge), null);
+    }
+
+    @Override
+    public void onChallengeRevoked(long challenge) {
+        handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
+                mUserId, challenge), null);
+    }
+
+    @Override
+    public void onAuthenticationFrame(AuthenticationFrame frame) {
+        handleResponse(FaceAuthenticationClient.class, (c) -> {
+            if (frame == null) {
+                Slog.e(TAG, "Received null enrollment frame for face authentication client.");
+                return;
+            }
+            c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
+        }, null);
+    }
+
+    @Override
+    public void onEnrollmentFrame(EnrollmentFrame frame) {
+        handleResponse(FaceEnrollClient.class, (c) -> {
+            if (frame == null) {
+                Slog.e(TAG, "Received null enrollment frame for face enroll client.");
+                return;
+            }
+            c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
+        }, null);
+    }
+
+    @Override
+    public void onError(byte error, int vendorCode) {
+        onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+    }
+
+    /**
+     * Handle error messages from the HAL.
+     */
+    public void onError(int error, int vendorCode) {
+        handleResponse(ErrorConsumer.class, (c) -> {
+            c.onError(error, vendorCode);
+            if (error == Error.HW_UNAVAILABLE) {
+                mHardwareUnavailableCallback.onHardwareUnavailable();
+            }
+        }, null);
+    }
+
+    @Override
+    public void onEnrollmentProgress(int enrollmentId, int remaining) {
+        BaseClientMonitor client = mScheduler.getCurrentClient();
+        final int currentUserId;
+        if (client == null) {
+            return;
+        } else {
+            currentUserId = client.getTargetUserId();
+        }
+        final CharSequence name = FaceUtils.getInstance(mSensorId)
+                .getUniqueName(mContext, currentUserId);
+        final Face face = new Face(name, enrollmentId, mSensorId);
+
+        handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+    }
+
+    @Override
+    public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+        final Face face = new Face("" /* name */, enrollmentId, mSensorId);
+        final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+        final ArrayList<Byte> byteList = new ArrayList<>();
+        for (byte b : byteArray) {
+            byteList.add(b);
+        }
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+                true /* authenticated */, byteList), null);
+    }
+
+    @Override
+    public void onAuthenticationFailed() {
+        final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+                false /* authenticated */, null /* hat */), null);
+    }
+
+    @Override
+    public void onLockoutTimed(long durationMillis) {
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+    }
+
+    @Override
+    public void onLockoutPermanent() {
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+    }
+
+    @Override
+    public void onLockoutCleared() {
+        handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
+                (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
+                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+    }
+
+    @Override
+    public void onInteractionDetected() {
+        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+    }
+
+    @Override
+    public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+        if (enrollmentIds.length > 0) {
+            for (int i = 0; i < enrollmentIds.length; ++i) {
+                final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
+                final int finalI = i;
+                handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
+                        enrollmentIds.length - finalI - 1), null);
+            }
+        } else {
+            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+                    null /* identifier */, 0 /* remaining */), null);
+        }
+    }
+
+    @Override
+    public void onFeaturesRetrieved(byte[] features) {
+        handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
+                features), null);
+    }
+
+    @Override
+    public void onFeatureSet(byte feature) {
+        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+    }
+
+    @Override
+    public void onEnrollmentsRemoved(int[] enrollmentIds) {
+        if (enrollmentIds.length > 0) {
+            for (int i = 0; i < enrollmentIds.length; i++) {
+                final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
+                final int finalI = i;
+                handleResponse(RemovalConsumer.class,
+                        (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
+                        null);
+            }
+        } else {
+            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+                    0 /* remaining */), null);
+        }
+    }
+
+    @Override
+    public void onAuthenticatorIdRetrieved(long authenticatorId) {
+        handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
+                authenticatorId), null);
+    }
+
+    @Override
+    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+        handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+                newAuthenticatorId), null);
+    }
+
+    /**
+     * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
+     */
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
+                null);
+    }
+
+    /**
+     * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL).
+     */
+    public void onLockoutChanged(long duration) {
+        mScheduler.getHandler().post(() -> {
+            @LockoutTracker.LockoutMode final int lockoutMode;
+            if (duration == 0) {
+                lockoutMode = LockoutTracker.LOCKOUT_NONE;
+            } else if (duration == -1 || duration == Long.MAX_VALUE) {
+                lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+            } else {
+                lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+            }
+
+            mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+
+            if (duration == 0) {
+                mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+            }
+        });
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> actionIfClassMatchesClient,
+            @Nullable Consumer<BaseClientMonitor> alternateAction) {
+        mScheduler.getHandler().post(() -> {
+            final BaseClientMonitor client = mScheduler.getCurrentClient();
+            if (className.isInstance(client)) {
+                actionIfClassMatchesClient.accept((T) client);
+            } else {
+                Slog.d(TAG, "Current client is not an instance of " + className.getName());
+                if (alternateAction != null) {
+                    alternateAction.accept(client);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSessionClosed() {
+        mScheduler.getHandler().post(mScheduler::onUserStopped);
+    }
+}
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 29eee6b..858bb86 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
@@ -16,10 +16,14 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
 import android.annotation.NonNull;
+import android.content.Context;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
 
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -31,14 +35,22 @@
     @NonNull
     private final ISession mSession;
     private final int mUserId;
-    @NonNull private final HalSessionCallback mHalSessionCallback;
+    @NonNull private final AidlResponseHandler mAidlResponseHandler;
 
     public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
-            HalSessionCallback halSessionCallback) {
+            AidlResponseHandler aidlResponseHandler) {
         mHalInterfaceVersion = halInterfaceVersion;
         mSession = session;
         mUserId = userId;
-        mHalSessionCallback = halSessionCallback;
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
+            AidlResponseHandler aidlResponseHandler) {
+        mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+        mHalInterfaceVersion = 0;
+        mUserId = userId;
+        mAidlResponseHandler = aidlResponseHandler;
     }
 
     /** The underlying {@link ISession}. */
@@ -52,8 +64,8 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    HalSessionCallback getHalSessionCallback() {
-        return mHalSessionCallback;
+    AidlResponseHandler getHalSessionCallback() {
+        return mAidlResponseHandler;
     }
 
     /**
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 35fc43a..470dc4b 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
@@ -46,8 +46,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
@@ -57,7 +57,8 @@
 /**
  * Face-specific authentication client for the {@link IFace} AIDL HAL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
+public class FaceAuthenticationClient
+        extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
         implements LockoutConsumer {
     private static final String TAG = "FaceAuthenticationClient";
 
@@ -74,11 +75,11 @@
     @Nullable
     private ICancellationSignal mCancellationSignal;
     @Nullable
-    private SensorPrivacyManager mSensorPrivacyManager;
+    private final SensorPrivacyManager mSensorPrivacyManager;
     @FaceManager.FaceAcquired
     private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
-    FaceAuthenticationClient(@NonNull Context context,
+    public FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, long operationId,
@@ -86,7 +87,7 @@
             boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
-            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
             @Authenticators.Types int sensorStrength) {
         this(context, lazyDaemon, token, requestId, listener, operationId,
                 restricted, options, cookie, requireConfirmation, logger, biometricContext,
@@ -103,12 +104,12 @@
             boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
-            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
-                isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
+                isStrongBiometric, null /* taskStackListener */, lockoutTracker,
                 allowBackgroundAuthentication, false /* shouldVibrate */,
                 biometricStrength);
         setRequestId(requestId);
@@ -263,8 +264,13 @@
         mLastAcquire = acquireInfo;
         final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
-        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
-        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+
+        //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL
+        if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser(
+                getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) {
+            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+        }
     }
 
     /**
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 f55cf05..dbed1f7 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
@@ -85,7 +85,7 @@
                 }
             };
 
-    FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 165c3a2..e404bd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -36,7 +36,7 @@
 public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
     private static final String TAG = "FaceGenerateChallengeClient";
 
-    FaceGenerateChallengeClient(@NonNull Context context,
+    public FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index ef3b345..c15049b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -33,6 +34,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -46,14 +48,16 @@
     private static final String TAG = "FaceGetFeatureClient";
 
     private final int mUserId;
+    private final int mFeature;
 
-    FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext, int feature) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 logger, biometricContext);
         mUserId = userId;
+        mFeature = feature;
     }
 
     @Override
@@ -70,7 +74,11 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().getFeatures();
+            ISession session = getFreshDaemon().getSession();
+            if (session instanceof AidlToHidlAdapter) {
+                ((AidlToHidlAdapter) session).setFeature(mFeature);
+            }
+            session.getFeatures();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to getFeature", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index f09d192..e75c6ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -38,9 +38,9 @@
 /**
  * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
  */
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
+public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
 
-    FaceInternalCleanupClient(@NonNull Context context,
+    public FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext,
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 cc3118c..dd9c6d5 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
@@ -493,7 +493,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
-                    mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
+                    mUsageStats, null /* lockoutTracker */,
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
@@ -619,7 +619,7 @@
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,
                     mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
-                    mBiometricContext);
+                    mBiometricContext, feature);
             scheduleForSensor(sensorId, client);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 0512017..0793888 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -36,12 +36,12 @@
 /**
  * Face-specific removal client for the {@link IFace} AIDL HAL interface.
  */
-class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
+public class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
     private static final String TAG = "FaceRemovalClient";
 
     final int[] mBiometricIds;
 
-    FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int[] biometricIds, int userId, @NonNull String owner,
             @NonNull BiometricUtils<Face> utils, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 1a12fcd..77b5592 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -32,7 +32,6 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -48,14 +47,14 @@
     private static final String TAG = "FaceResetLockoutClient";
 
     private final HardwareAuthToken mHardwareAuthToken;
-    private final LockoutCache mLockoutCache;
+    private final LockoutTracker mLockoutCache;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final int mBiometricStrength;
 
-    FaceResetLockoutClient(@NonNull Context context,
+    public FaceResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+            @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,7 +106,7 @@
      * be used instead.
      */
     static void resetLocalLockoutStateToNone(int sensorId, int userId,
-            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Authenticators.Types int biometricStrength, long requestId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 8838345..0d6143a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -38,7 +38,7 @@
 
     private final long mChallenge;
 
-    FaceRevokeChallengeClient(@NonNull Context context,
+    public FaceRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 6c14387..f6da872 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -46,7 +46,7 @@
     private final boolean mEnabled;
     private final HardwareAuthToken mHardwareAuthToken;
 
-    FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
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 2ad41c2..54e66eb 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
@@ -23,16 +23,10 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.AuthenticationFrame;
-import android.hardware.biometrics.face.EnrollmentFrame;
-import android.hardware.biometrics.face.Error;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
-import android.hardware.biometrics.face.ISessionCallback;
-import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -44,29 +38,22 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.RemovalConsumer;
 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.face.FaceUtils;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -91,397 +78,6 @@
     @NonNull private final Supplier<AidlSession> mLazySession;
     @Nullable AidlSession mCurrentSession;
 
-    @VisibleForTesting
-    public static class HalSessionCallback extends ISessionCallback.Stub {
-        /**
-         * Interface to sends results to the HalSessionCallback's owner.
-         */
-        public interface Callback {
-            /**
-             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-             */
-            void onHardwareUnavailable();
-        }
-
-        @NonNull
-        private final Context mContext;
-        @NonNull
-        private final Handler mHandler;
-        @NonNull
-        private final String mTag;
-        @NonNull
-        private final UserAwareBiometricScheduler mScheduler;
-        private final int mSensorId;
-        private final int mUserId;
-        @NonNull
-        private final LockoutCache mLockoutCache;
-        @NonNull
-        private final LockoutResetDispatcher mLockoutResetDispatcher;
-
-        @NonNull
-        private AuthSessionCoordinator mAuthSessionCoordinator;
-        @NonNull
-        private final Callback mCallback;
-
-        HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
-                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
-                @NonNull LockoutCache lockoutTracker,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull AuthSessionCoordinator authSessionCoordinator,
-                @NonNull Callback callback) {
-            mContext = context;
-            mHandler = handler;
-            mTag = tag;
-            mScheduler = scheduler;
-            mSensorId = sensorId;
-            mUserId = userId;
-            mLockoutCache = lockoutTracker;
-            mLockoutResetDispatcher = lockoutResetDispatcher;
-            mAuthSessionCoordinator = authSessionCoordinator;
-            mCallback = callback;
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-
-        @Override
-        public String getInterfaceHash() {
-            return this.HASH;
-        }
-
-        @Override
-        public void onChallengeGenerated(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceGenerateChallengeClient)) {
-                    Slog.e(mTag, "onChallengeGenerated for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceGenerateChallengeClient generateChallengeClient =
-                        (FaceGenerateChallengeClient) client;
-                generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onChallengeRevoked(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceRevokeChallengeClient)) {
-                    Slog.e(mTag, "onChallengeRevoked for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceRevokeChallengeClient revokeChallengeClient =
-                        (FaceRevokeChallengeClient) client;
-                revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFrame(AuthenticationFrame frame) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceAuthenticationClient)) {
-                    Slog.e(mTag, "onAuthenticationFrame for incompatible client: "
-                            + Utils.getClientName(client));
-                    return;
-
-                }
-                if (frame == null) {
-                    Slog.e(mTag, "Received null authentication frame for client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                ((FaceAuthenticationClient) client).onAuthenticationFrame(
-                        AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
-            });
-        }
-
-        @Override
-        public void onEnrollmentFrame(EnrollmentFrame frame) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceEnrollClient)) {
-                    Slog.e(mTag, "onEnrollmentFrame for incompatible client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                if (frame == null) {
-                    Slog.e(mTag, "Received null enrollment frame for client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                ((FaceEnrollClient) client).onEnrollmentFrame(
-                        AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
-            });
-        }
-
-        @Override
-        public void onError(byte error, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                Slog.d(mTag, "onError"
-                        + ", client: " + Utils.getClientName(client)
-                        + ", error: " + error
-                        + ", vendorCode: " + vendorCode);
-                if (!(client instanceof ErrorConsumer)) {
-                    Slog.e(mTag, "onError for non-error consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
-                if (error == Error.HW_UNAVAILABLE) {
-                    mCallback.onHardwareUnavailable();
-                }
-            });
-        }
-
-        @Override
-        public void onEnrollmentProgress(int enrollmentId, int remaining) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceEnrollClient)) {
-                    Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final int currentUserId = client.getTargetUserId();
-                final CharSequence name = FaceUtils.getInstance(mSensorId)
-                        .getUniqueName(mContext, currentUserId);
-                final Face face = new Face(name, enrollmentId, mSensorId);
-
-                final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
-                enrollClient.onEnrollResult(face, remaining);
-            });
-        }
-
-        @Override
-        public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Face face = new Face("" /* name */, enrollmentId, mSensorId);
-                final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
-                final ArrayList<Byte> byteList = new ArrayList<>();
-                for (byte b : byteArray) {
-                    byteList.add(b);
-                }
-                authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFailed() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
-                authenticationConsumer.onAuthenticated(face, false /* authenticated */,
-                        null /* hat */);
-            });
-        }
-
-        @Override
-        public void onLockoutTimed(long durationMillis) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutTimed(durationMillis);
-            });
-        }
-
-        @Override
-        public void onLockoutPermanent() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutPermanent();
-            });
-        }
-
-        @Override
-        public void onLockoutCleared() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceResetLockoutClient)) {
-                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
-                    // Given that onLockoutCleared() can happen at any time, and is not necessarily
-                    // coming from a specific client, set this to -1 to indicate it wasn't for a
-                    // specific request.
-                    FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
-                            mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                            Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
-                } else {
-                    Slog.d(mTag, "onLockoutCleared after resetLockout");
-                    final FaceResetLockoutClient resetLockoutClient =
-                            (FaceResetLockoutClient) client;
-                    resetLockoutClient.onLockoutCleared();
-                }
-            });
-        }
-
-        @Override
-        public void onInteractionDetected() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceDetectClient)) {
-                    Slog.e(mTag, "onInteractionDetected for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceDetectClient detectClient = (FaceDetectClient) client;
-                detectClient.onInteractionDetected();
-            });
-        }
-
-        @Override
-        public void onEnrollmentsEnumerated(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof EnumerateConsumer)) {
-                    Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final EnumerateConsumer enumerateConsumer =
-                        (EnumerateConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i = 0; i < enrollmentIds.length; ++i) {
-                        final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
-                        enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */);
-                }
-            });
-        }
-
-        @Override
-        public void onFeaturesRetrieved(byte[] features) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceGetFeatureClient)) {
-                    Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client;
-                faceGetFeatureClient.onFeatureGet(true /* success */, features);
-            });
-
-        }
-
-        @Override
-        public void onFeatureSet(byte feature) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceSetFeatureClient)) {
-                    Slog.e(mTag, "onFeatureSet for non-set consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client;
-                faceSetFeatureClient.onFeatureSet(true /* success */);
-            });
-        }
-
-        @Override
-        public void onEnrollmentsRemoved(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof RemovalConsumer)) {
-                    Slog.e(mTag, "onRemoved for non-removal consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i = 0; i < enrollmentIds.length; i++) {
-                        final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
-                        removalConsumer.onRemoved(face, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */);
-                }
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdRetrieved(long authenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceGetAuthenticatorIdClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceGetAuthenticatorIdClient getAuthenticatorIdClient =
-                        (FaceGetAuthenticatorIdClient) client;
-                getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceInvalidationClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client;
-                invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
-            });
-        }
-
-        @Override
-        public void onSessionClosed() {
-            mHandler.post(mScheduler::onUserStopped);
-        }
-    }
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@@ -511,9 +107,9 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final HalSessionCallback resultController = new HalSessionCallback(mContext,
-                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
-                                lockoutResetDispatcher,
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutCache, lockoutResetDispatcher,
                                 biometricContext.getAuthSessionCoordinator(), () -> {
                             Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
                             mCurrentSession = null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
new file mode 100644
index 0000000..eecf44b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.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.server.biometrics.sensors.face.hidl;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.EnrollmentStageConfig;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.common.NativeHandle;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+
+    private final String TAG = "AidlToHidlAdapter";
+    private static final int CHALLENGE_TIMEOUT_SEC = 600;
+    @DurationMillisLong
+    private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
+    @DurationMillisLong
+    private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
+    private static final int INVALID_VALUE = -1;
+    private final Clock mClock;
+    private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+    private long mGenerateChallengeCreatedAt = INVALID_VALUE;
+    private long mGenerateChallengeResult = INVALID_VALUE;
+    @NonNull private Supplier<IBiometricsFace> mSession;
+    private final int mUserId;
+    private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+    private final Context mContext;
+    private int mFeature = INVALID_VALUE;
+
+    public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+            int userId, AidlResponseHandler aidlResponseHandler) {
+        this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
+    }
+
+    AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+            AidlResponseHandler aidlResponseHandler, Clock clock) {
+        mSession = session;
+        mUserId = userId;
+        mContext = context;
+        mClock = clock;
+        setCallback(aidlResponseHandler);
+    }
+
+    private void setCallback(AidlResponseHandler aidlResponseHandler) {
+        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+        try {
+            mSession.get().setCallback(mHidlToAidlCallbackConverter);
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Failed to set callback");
+        }
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return null;
+    }
+
+    private boolean isGeneratedChallengeCacheValid() {
+        return mGenerateChallengeCreatedAt != INVALID_VALUE
+                && mGenerateChallengeResult != INVALID_VALUE
+                && mClock.millis() - mGenerateChallengeCreatedAt
+                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+    }
+
+    private void incrementChallengeCount() {
+        mGeneratedChallengeCount.add(0, mClock.millis());
+    }
+
+    private int decrementChallengeCount() {
+        final long now = mClock.millis();
+        // ignore values that are old in case generate/revoke calls are not matched
+        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+        if (!mGeneratedChallengeCount.isEmpty()) {
+            mGeneratedChallengeCount.remove(0);
+        }
+        return mGeneratedChallengeCount.size();
+    }
+
+    @Override
+    public void generateChallenge() throws RemoteException {
+        incrementChallengeCount();
+        if (isGeneratedChallengeCacheValid()) {
+            Slog.d(TAG, "Current challenge is cached and will be reused");
+            mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+            return;
+        }
+        mGenerateChallengeCreatedAt = mClock.millis();
+        mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+        mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+    }
+
+    @Override
+    public void revokeChallenge(long challenge) throws RemoteException {
+        final boolean shouldRevoke = decrementChallengeCount() == 0;
+        if (!shouldRevoke) {
+            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+                    + mGeneratedChallengeCount);
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+            return;
+        }
+        mGenerateChallengeCreatedAt = INVALID_VALUE;
+        mGenerateChallengeResult = INVALID_VALUE;
+        mSession.get().revokeChallenge();
+        mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+    }
+
+    @Override
+    public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
+        //unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features,
+            NativeHandle previewSurface) throws RemoteException {
+        final ArrayList<Byte> token = new ArrayList<>();
+        final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat);
+        for (byte b : hardwareAuthTokenArray) {
+            token.add(b);
+        }
+        final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+        for (byte b: features) {
+            disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b));
+        }
+        mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal authenticate(long operationId) throws RemoteException {
+        mSession.get().authenticate(operationId);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal detectInteraction() throws RemoteException {
+        mSession.get().authenticate(0);
+        return new Cancellation();
+    }
+
+    @Override
+    public void enumerateEnrollments() throws RemoteException {
+        mSession.get().enumerate();
+    }
+
+    @Override
+    public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+        mSession.get().remove(enrollmentIds[0]);
+    }
+
+    /**
+     * Needs to be called before getFeatures is invoked.
+     */
+    public void setFeature(int feature) {
+        mFeature = feature;
+    }
+
+    @Override
+    public void getFeatures() throws RemoteException {
+        final int faceId = getFaceId();
+        if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) {
+            return;
+        }
+
+        final OptionalBool result = mSession.get()
+                .getFeature(mFeature, faceId);
+
+        if (result.status == Status.OK && result.value) {
+            mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils
+                    .convertFrameworkToAidlFeature(mFeature)});
+        } else if (result.status == Status.OK) {
+            mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{});
+        } else {
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+        }
+
+        mFeature = INVALID_VALUE;
+    }
+
+    @Override
+    public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+            throws RemoteException {
+        final int faceId = getFaceId();
+        if (faceId == INVALID_VALUE) {
+            return;
+        }
+        ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>();
+        for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) {
+            hardwareAuthTokenList.add(b);
+        }
+        final int result = mSession.get().setFeature(
+                AidlConversionUtils.convertAidlToFrameworkFeature(feature),
+                enabled, hardwareAuthTokenList, faceId);
+        if (result == Status.OK) {
+            mHidlToAidlCallbackConverter.onFeatureSet(feature);
+        } else {
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+        }
+    }
+
+    private int getFaceId() {
+        FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+        List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+        if (faces.isEmpty()) {
+            Slog.d(TAG, "No faces to get feature from.");
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+            return INVALID_VALUE;
+        }
+
+        return faces.get(0).getBiometricId();
+    }
+
+    @Override
+    public void getAuthenticatorId() throws RemoteException {
+        long authenticatorId = mSession.get().getAuthenticatorId().value;
+        mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId);
+    }
+
+    @Override
+    public void invalidateAuthenticatorId() throws RemoteException {
+        //unsupported in HIDL
+    }
+
+    @Override
+    public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+        ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+        for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+            hardwareAuthToken.add(b);
+        }
+        mSession.get().resetLockout(hardwareAuthToken);
+    }
+
+    @Override
+    public void close() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
+            NativeHandle previewSurface, OperationContext context) throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal detectInteractionWithContext(OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public void onContextChanged(OperationContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        //Unsupported in HIDL
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    /**
+     * Cancellation in HIDL occurs for the entire session, instead of a specific client.
+     */
+    private class Cancellation extends ICancellationSignal.Stub {
+
+        Cancellation() {}
+        @Override
+        public void cancel() throws RemoteException {
+            try {
+                mSession.get().cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when requesting cancel", e);
+            }
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return 0;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return null;
+        }
+    }
+}
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 1499317..46ce0b6 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
@@ -54,6 +54,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -61,6 +62,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 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.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -78,6 +80,8 @@
 import com.android.server.biometrics.sensors.face.LockoutHalImpl;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -131,7 +135,9 @@
     private int mCurrentUserId = UserHandle.USER_NULL;
     private final int mSensorId;
     private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
     private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
+    private AidlSession mSession;
 
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
@@ -361,6 +367,7 @@
         mLockoutTracker = new LockoutHalImpl();
         mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
                 mScheduler, mLockoutTracker, lockoutResetDispatcher);
+        mLockoutResetDispatcher = lockoutResetDispatcher;
         mHalResultController.setCallback(() -> {
             mDaemon = null;
             mCurrentUserId = UserHandle.USER_NULL;
@@ -420,6 +427,24 @@
         });
     }
 
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    synchronized AidlSession getSession() {
+        if (mDaemon != null && mSession != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId,
+                    new AidlResponseHandler(mContext, mScheduler, mSensorId,
+                            mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+                            new AuthSessionCoordinator(), () -> {
+                        mDaemon = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }));
+        }
+    }
+
     private synchronized IBiometricsFace getDaemon() {
         if (mTestHalEnabled) {
             final TestHal testHal = new TestHal(mContext, mSensorId);
@@ -551,32 +576,63 @@
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            incrementChallengeCount();
-
-            if (isGeneratedChallengeCacheValid()) {
-                Slog.d(TAG, "Current challenge is cached and will be reused");
-                mGeneratedChallengeCache.reuseResult(receiver);
-                return;
-            }
-
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, sSystemClock.millis());
-            mGeneratedChallengeCache = client;
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    if (client != clientMonitor) {
-                        Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client."
-                                + " Expecting: " + client + ", received: " + clientMonitor);
-                    }
+            if (Flags.deHidl()) {
+                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+            } else {
+                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+            }
+        });
+    }
+
+    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                        mBiometricContext);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleGenerateChallenge onClientStarted, mismatched client."
+                                    + " Expecting: " + client + ", received: "
+                                    + clientMonitor);
                 }
-            });
+            }
+        });
+    }
+
+    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        incrementChallengeCount();
+        if (isGeneratedChallengeCacheValid()) {
+            Slog.d(TAG, "Current challenge is cached and will be reused");
+            mGeneratedChallengeCache.reuseResult(receiver);
+            return;
+        }
+
+        final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+                opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, sSystemClock.millis());
+        mGeneratedChallengeCache = client;
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleGenerateChallenge onClientStarted, mismatched client."
+                                    + " Expecting: " + client + ", received: "
+                                    + clientMonitor);
+                }
+            }
         });
     }
 
@@ -584,31 +640,62 @@
     public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
-            final boolean shouldRevoke = decrementChallengeCount() == 0;
-            if (!shouldRevoke) {
-                Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
-                        + mGeneratedChallengeCount);
-                return;
+            if (Flags.deHidl()) {
+                scheduleRevokeChallengeAidl(userId, token, opPackageName);
+            } else {
+                scheduleRevokeChallengeHidl(userId, token, opPackageName);
             }
+        });
+    }
 
-            Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
-            mGeneratedChallengeCache = null;
-
-            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                    mLazyDaemon, token, userId, opPackageName, mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    if (client != clientMonitor) {
-                        Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
-                                + "Expecting: " + client + ", received: " + clientMonitor);
-                    }
+    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient
+                client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient(
+                        mContext, this::getSession, token, userId, opPackageName, mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector), mBiometricContext, 0L);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+                                    + client + ", received: " + clientMonitor);
                 }
-            });
+            }
+        });
+    }
+
+    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final boolean shouldRevoke = decrementChallengeCount() == 0;
+        if (!shouldRevoke) {
+            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+                    + mGeneratedChallengeCount);
+            return;
+        }
+
+        Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
+        mGeneratedChallengeCache = null;
+        final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+                mLazyDaemon, token, userId, opPackageName, mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+                                    + client + ", received: " + clientMonitor);
+                }
+            }
         });
     }
 
@@ -620,7 +707,62 @@
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
+            if (Flags.deHidl()) {
+                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, disabledFeatures, previewSurface, id);
+            } else {
+                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, disabledFeatures, previewSurface, id);
+            }
+        });
+        return id;
+    }
 
+    private void scheduleEnrollAidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+            @Nullable Surface previewSurface, long id) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId,
+                        hardwareAuthToken, opPackageName, id,
+                        FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+                        ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector), mBiometricContext,
+                        mContext.getResources().getInteger(
+                                com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
+                        false);
+
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                mBiometricStateCallback.onClientStarted(clientMonitor);
+            }
+
+            @Override
+            public void onBiometricAction(int action) {
+                mBiometricStateCallback.onBiometricAction(action);
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+                }
+            }
+        });
+    }
+
+    private void scheduleEnrollHidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+            @Nullable Surface previewSurface, long id) {
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
@@ -628,7 +770,6 @@
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
-
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -650,8 +791,6 @@
                     }
                 }
             });
-        });
-        return id;
     }
 
     @Override
@@ -683,18 +822,46 @@
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
-            final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
-                    mLazyDaemon, token, requestId, receiver, operationId, restricted,
-                    options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, isStrongBiometric, mLockoutTracker,
-                    mUsageStats, allowBackgroundAuthentication,
-                    Utils.getCurrentStrength(mSensorId));
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            } else {
+                scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            }
         });
     }
 
+    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient
+                client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient(
+                        mContext, this::getSession, token, requestId, receiver, operationId,
+                        restricted, options, cookie, false /* requireConfirmation */,
+                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                                mAuthenticationStatsCollector), mBiometricContext,
+                        isStrongBiometric, mUsageStats, mLockoutTracker,
+                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+                mLazyDaemon, token, requestId, receiver, operationId, restricted, options,
+                cookie, false /* requireConfirmation */,
+                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                        mAuthenticationStatsCollector), mBiometricContext,
+                isStrongBiometric, mLockoutTracker, mUsageStats,
+                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter receiver,
@@ -719,13 +886,11 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
+            } else {
+                scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
+            }
         });
     }
 
@@ -736,17 +901,39 @@
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             // For IBiometricsFace@1.0, remove(0) means remove all enrollments
-            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
-                    opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, userId, receiver, opPackageName, 0);
+            } else {
+                scheduleRemoveHidl(token, userId, receiver, opPackageName, 0);
+            }
         });
     }
 
+    private void scheduleRemoveAidl(@NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId,
+                        opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector), mBiometricContext,
+                        mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleRemoveHidl(@NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+        final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+                new ClientMonitorCallbackConverter(receiver), faceId, userId,
+                opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
         mHandler.post(() -> {
@@ -756,89 +943,180 @@
             }
 
             scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
-                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, hardwareAuthToken);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleResetLockoutAidl(userId, hardwareAuthToken);
+            } else {
+                scheduleResetLockoutHidl(userId, hardwareAuthToken);
+            }
         });
     }
 
+    private void scheduleResetLockoutAidl(int userId,
+            @NonNull byte[] hardwareAuthToken) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, hardwareAuthToken, mLockoutTracker,
+                        mLockoutResetDispatcher, mSensorProperties.sensorStrength);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleResetLockoutHidl(int userId,
+            @NonNull byte[] hardwareAuthToken) {
+        final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+                mLazyDaemon,
+                userId, mContext.getOpPackageName(), mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, hardwareAuthToken);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             boolean enabled, @NonNull byte[] hardwareAuthToken,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final List<Face> faces = getEnrolledFaces(sensorId, userId);
-            if (faces.isEmpty()) {
-                Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
-                return;
-            }
-
             scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final int faceId = faces.get(0).getBiometricId();
-            final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
-                    mBiometricContext,
-                    feature, enabled, hardwareAuthToken, faceId);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+                        receiver, opPackageName);
+            } else {
+                scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+                        receiver, opPackageName);
+            }
         });
     }
 
+    private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final List<Face> faces = getEnrolledFaces(sensorId, userId);
+        if (faces.isEmpty()) {
+            Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+            return;
+        }
+        final int faceId = faces.get(0).getBiometricId();
+        final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon,
+                token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature,
+                enabled, hardwareAuthToken, faceId);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId,
+            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                        feature, enabled, hardwareAuthToken);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+
     @Override
     public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final List<Face> faces = getEnrolledFaces(sensorId, userId);
-            if (faces.isEmpty()) {
-                Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
-                return;
-            }
-
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final int faceId = faces.get(0).getBiometricId();
-            final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
-                    token, listener, userId, opPackageName, mSensorId,
-                    BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                    feature, faceId);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientFinished(
-                        @NonNull BaseClientMonitor clientMonitor, boolean success) {
-                    if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
-                        final int settingsValue = client.getValue() ? 1 : 0;
-                        Slog.d(TAG, "Updating attention value for user: " + userId
-                                + " to value: " + settingsValue);
-                        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                                Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
-                                settingsValue, userId);
-                    }
-                }
-            });
+            if (Flags.deHidl()) {
+                scheduleGetFeatureAidl(token, userId, feature, listener,
+                        opPackageName);
+            } else {
+                scheduleGetFeatureHidl(sensorId, token, userId, feature, listener,
+                        opPackageName);
+            }
         });
     }
 
+    private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+            int feature, @Nullable ClientMonitorCallbackConverter listener,
+            @NonNull String opPackageName) {
+        final List<Face> faces = getEnrolledFaces(sensorId, userId);
+        if (faces.isEmpty()) {
+            Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+            return;
+        }
+
+        final int faceId = faces.get(0).getBiometricId();
+        final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
+                token, listener, userId, opPackageName, mSensorId,
+                BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                if (success
+                        && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
+                    final int settingsValue = client.getValue() ? 1 : 0;
+                    Slog.d(TAG,
+                            "Updating attention value for user: " + userId + " to value: "
+                                    + settingsValue);
+                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                            Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue,
+                            userId);
+                }
+            }
+        });
+    }
+
+    private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId,
+            int feature, @Nullable ClientMonitorCallbackConverter listener,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient(
+                        mContext, this::getSession, token, listener, userId, opPackageName,
+                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                        feature);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     private void scheduleInternalCleanup(int userId,
             @Nullable ClientMonitorCallback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
-                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext,
-                    FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
-                    mBiometricStateCallback));
+            if (Flags.deHidl()) {
+                scheduleInternalCleanupAidl(userId, callback);
+            } else {
+                scheduleInternalCleanupHidl(userId, callback);
+            }
         });
     }
 
+    private void scheduleInternalCleanupHidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+                mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+                mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client,
+                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+    }
+
+    private void scheduleInternalCleanupAidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient
+                client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                        mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+                        mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client,
+                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+    }
+
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback) {
@@ -970,6 +1248,10 @@
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
                 if (success) {
+                    if (mCurrentUserId != targetUserId) {
+                        // Create new session with updated user ID
+                        mSession = null;
+                    }
                     mCurrentUserId = targetUserId;
                 } else {
                     Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
new file mode 100644
index 0000000..36a9790
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.hidl;
+
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub {
+
+    private final AidlResponseHandler mAidlResponseHandler;
+
+    public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) {
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    @Override
+    public void onEnrollResult(
+            long deviceId, int faceId, int userId, int remaining) {
+        mAidlResponseHandler.onEnrollmentProgress(faceId, remaining);
+    }
+
+    @Override
+    public void onAuthenticated(long deviceId, int faceId, int userId,
+            ArrayList<Byte> token) {
+        final boolean authenticated = faceId != 0;
+        byte[] hardwareAuthToken = new byte[token.size()];
+
+        for (int i = 0; i < token.size(); i++) {
+            hardwareAuthToken[i] = token.get(i);
+        }
+
+        if (authenticated) {
+            mAidlResponseHandler.onAuthenticationSucceeded(faceId,
+                    HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+        } else {
+            mAidlResponseHandler.onAuthenticationFailed();
+        }
+    }
+
+    @Override
+    public void onAcquired(long deviceId, int userId, int acquiredInfo,
+            int vendorCode) {
+        mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+    }
+
+    @Override
+    public void onError(long deviceId, int userId, int error, int vendorCode) {
+        mAidlResponseHandler.onError(error, vendorCode);
+    }
+
+    @Override
+    public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+        int[] enrollmentIds = new int[removed.size()];
+        for (int i = 0; i < removed.size(); i++) {
+            enrollmentIds[i] = removed.get(i);
+        }
+        mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds);
+    }
+
+    @Override
+    public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+        int[] enrollmentIds = new int[faceIds.size()];
+        for (int i = 0; i < faceIds.size(); i++) {
+            enrollmentIds[i] = faceIds.get(i);
+        }
+        mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds);
+    }
+
+    @Override
+    public void onLockoutChanged(long duration) {
+        mAidlResponseHandler.onLockoutChanged(duration);
+    }
+
+    void onChallengeGenerated(long challenge) {
+        mAidlResponseHandler.onChallengeGenerated(challenge);
+    }
+
+    void onChallengeRevoked(long challenge) {
+        mAidlResponseHandler.onChallengeRevoked(challenge);
+    }
+
+    void onFeatureGet(byte[] features) {
+        mAidlResponseHandler.onFeaturesRetrieved(features);
+    }
+
+    void onFeatureSet(byte feature) {
+        mAidlResponseHandler.onFeatureSet(feature);
+    }
+
+    void onAuthenticatorIdRetrieved(long authenticatorId) {
+        mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
new file mode 100644
index 0000000..4a01943
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+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.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface HardwareUnavailableCallback {
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
+    private static final String TAG = "AidlResponseHandler";
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricScheduler mScheduler;
+    private final int mSensorId;
+    private final int mUserId;
+    @NonNull
+    private final LockoutCache mLockoutCache;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        mContext = context;
+        mScheduler = scheduler;
+        mSensorId = sensorId;
+        mUserId = userId;
+        mLockoutCache = lockoutTracker;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mHardwareUnavailableCallback = hardwareUnavailableCallback;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return this.HASH;
+    }
+
+    @Override
+    public void onChallengeGenerated(long challenge) {
+        handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
+                mSensorId, mUserId, challenge), null);
+    }
+
+    @Override
+    public void onChallengeRevoked(long challenge) {
+        handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
+                challenge), null);
+    }
+
+    /**
+     * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
+     */
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
+                null);
+    }
+
+    @Override
+    public void onAcquired(byte info, int vendorCode) {
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
+                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+    }
+
+    /**
+     * Handle error messages from the HAL.
+     */
+    public void onError(int error, int vendorCode) {
+        handleResponse(ErrorConsumer.class, (c) -> {
+            c.onError(error, vendorCode);
+            if (error == Error.HW_UNAVAILABLE) {
+                mHardwareUnavailableCallback.onHardwareUnavailable();
+            }
+        }, null);
+    }
+
+    @Override
+    public void onError(byte error, int vendorCode) {
+        onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+    }
+
+    @Override
+    public void onEnrollmentProgress(int enrollmentId, int remaining) {
+        BaseClientMonitor client = mScheduler.getCurrentClient();
+        final int currentUserId;
+        if (client == null) {
+            return;
+        } else {
+            currentUserId = client.getTargetUserId();
+        }
+        final CharSequence name = FingerprintUtils.getInstance(mSensorId)
+                .getUniqueName(mContext, currentUserId);
+        final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
+                enrollmentId, mSensorId);
+        handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
+                remaining), null);
+    }
+
+    @Override
+    public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+        final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+        final ArrayList<Byte> byteList = new ArrayList<>();
+        for (byte b : byteArray) {
+            byteList.add(b);
+        }
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+                true /* authenticated */, byteList), (c) -> onInteractionDetected());
+    }
+
+    @Override
+    public void onAuthenticationFailed() {
+        final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+                false /* authenticated */, null /* hardwareAuthToken */),
+                (c) -> onInteractionDetected());
+    }
+
+    @Override
+    public void onLockoutTimed(long durationMillis) {
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
+                null);
+    }
+
+    @Override
+    public void onLockoutPermanent() {
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+    }
+
+    @Override
+    public void onLockoutCleared() {
+        handleResponse(FingerprintResetLockoutClient.class,
+                FingerprintResetLockoutClient::onLockoutCleared,
+                (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
+                        mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+                        mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
+                        -1 /* requestId */));
+    }
+
+    @Override
+    public void onInteractionDetected() {
+        handleResponse(FingerprintDetectClient.class,
+                FingerprintDetectClient::onInteractionDetected, null);
+    }
+
+    @Override
+    public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+        if (enrollmentIds.length > 0) {
+            for (int i = 0; i < enrollmentIds.length; i++) {
+                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+                int finalI = i;
+                handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
+                        enrollmentIds.length - finalI - 1), null);
+            }
+        } else {
+            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
+                    0), null);
+        }
+    }
+
+    @Override
+    public void onEnrollmentsRemoved(int[] enrollmentIds) {
+        if (enrollmentIds.length > 0) {
+            for (int i  = 0; i < enrollmentIds.length; i++) {
+                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+                int finalI = i;
+                handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
+                        enrollmentIds.length - finalI - 1), null);
+            }
+        } else {
+            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
+                    null);
+        }
+    }
+
+    @Override
+    public void onAuthenticatorIdRetrieved(long authenticatorId) {
+        handleResponse(FingerprintGetAuthenticatorIdClient.class,
+                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+    }
+
+    @Override
+    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+        handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+                newAuthenticatorId), null);
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> action,
+            @Nullable Consumer<BaseClientMonitor> alternateAction) {
+        mScheduler.getHandler().post(() -> {
+            final BaseClientMonitor client = mScheduler.getCurrentClient();
+            if (className.isInstance(client)) {
+                action.accept((T) client);
+            } else {
+                Slog.e(TAG, "Client monitor is not an instance of " + className.getName());
+                if (alternateAction != null) {
+                    alternateAction.accept(client);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSessionClosed() {
+        mScheduler.getHandler().post(mScheduler::onUserStopped);
+    }
+}
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 55861bb..299a310 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
@@ -16,10 +16,13 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
 import android.annotation.NonNull;
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+
+import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
 
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -30,14 +33,22 @@
     private final int mHalInterfaceVersion;
     @NonNull private final ISession mSession;
     private final int mUserId;
-    @NonNull private final HalSessionCallback mHalSessionCallback;
+    @NonNull private final AidlResponseHandler mAidlResponseHandler;
 
     public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
-            HalSessionCallback halSessionCallback) {
+            AidlResponseHandler aidlResponseHandler) {
         mHalInterfaceVersion = halInterfaceVersion;
         mSession = session;
         mUserId = userId;
-        mHalSessionCallback = halSessionCallback;
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
+            int userId, AidlResponseHandler aidlResponseHandler) {
+        mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+        mHalInterfaceVersion = 0;
+        mUserId = userId;
+        mAidlResponseHandler = aidlResponseHandler;
     }
 
     /** The underlying {@link ISession}. */
@@ -51,8 +62,8 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    HalSessionCallback getHalSessionCallback() {
-        return mHalSessionCallback;
+    AidlResponseHandler getHalSessionCallback() {
+        return mAidlResponseHandler;
     }
 
     /**
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 54d1faa..337c3c2 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
@@ -21,6 +21,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -51,8 +52,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
@@ -66,7 +67,7 @@
  * Fingerprint-specific authentication client supporting the {@link
  * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintAuthenticationClient
+public class FingerprintAuthenticationClient
         extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
@@ -93,7 +94,7 @@
     private Runnable mAuthSuccessRunnable;
     private final Clock mClock;
 
-    FingerprintAuthenticationClient(
+    public FingerprintAuthenticationClient(
             @NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
@@ -108,14 +109,14 @@
             @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric,
             @Nullable TaskStackListener taskStackListener,
-            @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
             @Authenticators.Types int biometricStrength,
-            @NonNull Clock clock) {
+            @NonNull Clock clock,
+            @Nullable LockoutTracker lockoutTracker) {
         super(
                 context,
                 lazyDaemon,
@@ -130,7 +131,7 @@
                 biometricContext,
                 isStrongBiometric,
                 taskStackListener,
-                null /* lockoutCache */,
+                lockoutTracker,
                 allowBackgroundAuthentication,
                 false /* shouldVibrate */,
                 biometricStrength);
@@ -211,6 +212,7 @@
             boolean authenticated,
             ArrayList<Byte> token) {
         super.onAuthenticated(identifier, authenticated, token);
+        handleLockout(authenticated);
         if (authenticated) {
             mState = STATE_STOPPED;
             mSensorOverlays.hide(getSensorId());
@@ -219,6 +221,32 @@
         }
     }
 
+    private void handleLockout(boolean authenticated) {
+        if (getLockoutTracker() == null) {
+            Slog.d(TAG, "Lockout is implemented by the HAL");
+            return;
+        }
+        if (authenticated) {
+            getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    getTargetUserId());
+        } else {
+            @LockoutTracker.LockoutMode final int lockoutMode =
+                    getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+            if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+                Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
+                final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
+                        ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+                        : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+                // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
+                // controlled by the HAL, the framework must stop the sensor before finishing the
+                // client.
+                mSensorOverlays.hide(getSensorId());
+                onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
+                cancel();
+            }
+        }
+    }
+
     @Override
     public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
         // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
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 4502e5d..e2413ee 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
@@ -43,7 +43,8 @@
  * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
  * have the same haptic, lockout counter is not increased).
  */
-class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
+public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
+        implements DetectionConsumer {
 
     private static final String TAG = "FingerprintDetectClient";
 
@@ -52,7 +53,8 @@
     @NonNull private final SensorOverlays mSensorOverlays;
     @Nullable private ICancellationSignal mCancellationSignal;
 
-    FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FingerprintDetectClient(@NonNull Context context,
+            @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener,
             @NonNull FingerprintAuthenticateOptions options,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 46ff6b4..06550d8 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
@@ -49,14 +49,13 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
 import java.util.function.Supplier;
 
-class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
+public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
         PowerPressHandler {
 
     private static final String TAG = "FingerprintEnrollClient";
@@ -72,12 +71,16 @@
 
     private static boolean shouldVibrateFor(Context context,
             FingerprintSensorPropertiesInternal sensorProps) {
-        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
-        final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
-        return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+        if (sensorProps != null) {
+            final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+            final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
+            return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+        } else {
+            return true;
+        }
     }
 
-    FingerprintEnrollClient(@NonNull Context context,
+    public FingerprintEnrollClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
@@ -89,8 +92,8 @@
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger,
-                biometricContext);
+                0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps),
+                logger, biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
@@ -136,7 +139,7 @@
                 acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
         // For UDFPS, notify SysUI that the illumination can be turned off.
         // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
-        if (mSensorProps.isAnyUdfpsType()) {
+        if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) {
             if (acquiredGood && mShouldVibrate) {
                 vibrateSuccess();
             }
@@ -162,8 +165,7 @@
 
     @Override
     protected boolean hasReachedEnrollmentLimit() {
-        return FingerprintUtils.getInstance(getSensorId())
-                .getBiometricsForUser(getContext(), getTargetUserId()).size()
+        return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size()
                 >= mMaxTemplatesPerUser;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ddae8be..ce693ff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -33,10 +33,10 @@
 /**
  * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
  */
-class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
+public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
     private static final String TAG = "FingerprintGenerateChallengeClient";
 
-    FingerprintGenerateChallengeClient(@NonNull Context context,
+    public FingerprintGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index ff9127f..5edc2ca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -39,9 +39,10 @@
  * Fingerprint-specific internal cleanup client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
+public class FingerprintInternalCleanupClient
+        extends InternalCleanupClient<Fingerprint, AidlSession> {
 
-    FingerprintInternalCleanupClient(@NonNull Context context,
+    public FingerprintInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
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 e42b664..9985b06 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
@@ -428,8 +428,8 @@
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                    opPackageName, FingerprintUtils.getInstance(sensorId),
+                    sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
@@ -496,12 +496,13 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
-                    mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
+                    mTaskStackListener,
                     mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication,
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
-                    SystemClock.elapsedRealtimeClock());
+                    SystemClock.elapsedRealtimeClock(),
+                    null /* lockoutTracker */);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
 
                 @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index d559bb1..4f08f6f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -37,12 +37,12 @@
  * Fingerprint-specific removal client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.
  */
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
+public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
     private static final String TAG = "FingerprintRemovalClient";
 
     private final int[] mBiometricIds;
 
-    FingerprintRemovalClient(@NonNull Context context,
+    public FingerprintRemovalClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 7a62034..ec225a6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -32,7 +32,6 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -43,19 +42,20 @@
  * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
  * cleared.
  */
-class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
+public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession>
+        implements ErrorConsumer {
 
     private static final String TAG = "FingerprintResetLockoutClient";
 
     private final HardwareAuthToken mHardwareAuthToken;
-    private final LockoutCache mLockoutCache;
+    private final LockoutTracker mLockoutCache;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final int mBiometricStrength;
 
-    FingerprintResetLockoutClient(@NonNull Context context,
+    public FingerprintResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
-            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+            @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,10 +107,11 @@
      * be used instead.
      */
     static void resetLocalLockoutStateToNone(int sensorId, int userId,
-            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Authenticators.Types int biometricStrength, long requestId) {
+        lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
         lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
         lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
         authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index afa62e2..23d8e1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -32,13 +32,13 @@
 /**
  * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
  */
-class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
+public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
 
-    private static final String TAG = "FingerpirntRevokeChallengeClient";
+    private static final String TAG = "FingerprintRevokeChallengeClient";
 
     private final long mChallenge;
 
-    FingerprintRevokeChallengeClient(@NonNull Context context,
+    public FingerprintRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@@ -57,7 +57,7 @@
         }
     }
 
-    void onChallengeRevoked(int sensorId, int userId, long challenge) {
+    void onChallengeRevoked(long challenge) {
         final boolean success = challenge == mChallenge;
         mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, 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 56b85ce..893cb8f 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
@@ -23,13 +23,9 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.biometrics.fingerprint.ISessionCallback;
-import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,34 +35,25 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.RemovalConsumer;
 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.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -93,348 +80,6 @@
     @Nullable AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
-    @VisibleForTesting
-    public static class HalSessionCallback extends ISessionCallback.Stub {
-
-        /**
-         * Interface to sends results to the HalSessionCallback's owner.
-         */
-        public interface Callback {
-            /**
-             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-             */
-            void onHardwareUnavailable();
-        }
-
-        @NonNull
-        private final Context mContext;
-        @NonNull
-        private final Handler mHandler;
-        @NonNull
-        private final String mTag;
-        @NonNull
-        private final UserAwareBiometricScheduler mScheduler;
-        private final int mSensorId;
-        private final int mUserId;
-        @NonNull
-        private final LockoutCache mLockoutCache;
-        @NonNull
-        private final LockoutResetDispatcher mLockoutResetDispatcher;
-        @NonNull
-        private AuthSessionCoordinator mAuthSessionCoordinator;
-        @NonNull
-        private final Callback mCallback;
-
-        HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
-                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
-                @NonNull LockoutCache lockoutTracker,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull AuthSessionCoordinator authSessionCoordinator,
-                @NonNull Callback callback) {
-            mContext = context;
-            mHandler = handler;
-            mTag = tag;
-            mScheduler = scheduler;
-            mSensorId = sensorId;
-            mUserId = userId;
-            mLockoutCache = lockoutTracker;
-            mLockoutResetDispatcher = lockoutResetDispatcher;
-            mAuthSessionCoordinator = authSessionCoordinator;
-            mCallback = callback;
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-
-        @Override
-        public String getInterfaceHash() {
-            return this.HASH;
-        }
-
-        @Override
-        public void onChallengeGenerated(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintGenerateChallengeClient)) {
-                    Slog.e(mTag, "onChallengeGenerated for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintGenerateChallengeClient generateChallengeClient =
-                        (FingerprintGenerateChallengeClient) client;
-                generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onChallengeRevoked(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintRevokeChallengeClient)) {
-                    Slog.e(mTag, "onChallengeRevoked for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintRevokeChallengeClient revokeChallengeClient =
-                        (FingerprintRevokeChallengeClient) client;
-                revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onAcquired(byte info, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AcquisitionClient)) {
-                    Slog.e(mTag, "onAcquired for non-acquisition client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
-                acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info),
-                        vendorCode);
-            });
-        }
-
-        @Override
-        public void onError(byte error, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                Slog.d(mTag, "onError"
-                        + ", client: " + Utils.getClientName(client)
-                        + ", error: " + error
-                        + ", vendorCode: " + vendorCode);
-                if (!(client instanceof ErrorConsumer)) {
-                    Slog.e(mTag, "onError for non-error consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
-                if (error == Error.HW_UNAVAILABLE) {
-                    mCallback.onHardwareUnavailable();
-                }
-            });
-        }
-
-        @Override
-        public void onEnrollmentProgress(int enrollmentId, int remaining) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintEnrollClient)) {
-                    Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final int currentUserId = client.getTargetUserId();
-                final CharSequence name = FingerprintUtils.getInstance(mSensorId)
-                        .getUniqueName(mContext, currentUserId);
-                final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId);
-
-                final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
-                enrollClient.onEnrollResult(fingerprint, remaining);
-            });
-        }
-
-        @Override
-        public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
-                final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
-                final ArrayList<Byte> byteList = new ArrayList<>();
-                for (byte b : byteArray) {
-                    byteList.add(b);
-                }
-
-                authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFailed() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
-                authenticationConsumer
-                        .onAuthenticated(fp, false /* authenticated */, null /* hat */);
-            });
-        }
-
-        @Override
-        public void onLockoutTimed(long durationMillis) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutTimed(durationMillis);
-            });
-        }
-
-        @Override
-        public void onLockoutPermanent() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutPermanent();
-            });
-        }
-
-        @Override
-        public void onLockoutCleared() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintResetLockoutClient)) {
-                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
-                    // Given that onLockoutCleared() can happen at any time, and is not necessarily
-                    // coming from a specific client, set this to -1 to indicate it wasn't for a
-                    // specific request.
-                    FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
-                            mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                            Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
-                } else {
-                    Slog.d(mTag, "onLockoutCleared after resetLockout");
-                    final FingerprintResetLockoutClient resetLockoutClient =
-                            (FingerprintResetLockoutClient) client;
-                    resetLockoutClient.onLockoutCleared();
-                }
-            });
-        }
-
-        @Override
-        public void onInteractionDetected() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintDetectClient)) {
-                    Slog.e(mTag, "onInteractionDetected for non-detect client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintDetectClient fingerprintDetectClient =
-                        (FingerprintDetectClient) client;
-                fingerprintDetectClient.onInteractionDetected();
-            });
-        }
-
-        @Override
-        public void onEnrollmentsEnumerated(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof EnumerateConsumer)) {
-                    Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final EnumerateConsumer enumerateConsumer =
-                        (EnumerateConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i = 0; i < enrollmentIds.length; i++) {
-                        final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                        enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
-                }
-            });
-        }
-
-        @Override
-        public void onEnrollmentsRemoved(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof RemovalConsumer)) {
-                    Slog.e(mTag, "onRemoved for non-removal consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i  = 0; i < enrollmentIds.length; i++) {
-                        final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                        removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    removalConsumer.onRemoved(null, 0);
-                }
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdRetrieved(long authenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
-                        (FingerprintGetAuthenticatorIdClient) client;
-                getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintInvalidationClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintInvalidationClient invalidationClient =
-                        (FingerprintInvalidationClient) client;
-                invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
-            });
-        }
-
-        @Override
-        public void onSessionClosed() {
-            mHandler.post(mScheduler::onUserStopped);
-        }
-    }
-
     Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -466,9 +111,9 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final HalSessionCallback resultController = new HalSessionCallback(mContext,
-                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
-                                lockoutResetDispatcher,
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutCache, lockoutResetDispatcher,
                                 biometricContext.getAuthSessionCoordinator(), () -> {
                             Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
                             mCurrentSession = null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
new file mode 100644
index 0000000..b48d232
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+    private final String TAG = "AidlToHidlAdapter";
+    @VisibleForTesting
+    static final int ENROLL_TIMEOUT_SEC = 60;
+    @NonNull
+    private final Supplier<IBiometricsFingerprint> mSession;
+    private final int mUserId;
+    private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+
+    public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+            AidlResponseHandler aidlResponseHandler) {
+        mSession = session;
+        mUserId = userId;
+        setCallback(aidlResponseHandler);
+    }
+
+    private void setCallback(AidlResponseHandler aidlResponseHandler) {
+        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+        try {
+            mSession.get().setNotify(mHidlToAidlCallbackConverter);
+        } catch (RemoteException e) {
+            Slog.d(TAG, "Failed to set callback");
+        }
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return null;
+    }
+
+    @Override
+    public void generateChallenge() throws RemoteException {
+        long challenge = mSession.get().preEnroll();
+        mHidlToAidlCallbackConverter.onChallengeGenerated(challenge);
+    }
+
+    @Override
+    public void revokeChallenge(long challenge) throws RemoteException {
+        mSession.get().postEnroll();
+        mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+    }
+
+    @Override
+    public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException {
+        mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId,
+                ENROLL_TIMEOUT_SEC);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal authenticate(long operationId) throws RemoteException {
+        mSession.get().authenticate(operationId, mUserId);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal detectInteraction() throws RemoteException {
+        mSession.get().authenticate(0, mUserId);
+        return new Cancellation();
+    }
+
+    @Override
+    public void enumerateEnrollments() throws RemoteException {
+        mSession.get().enumerate();
+    }
+
+    @Override
+    public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+        if (enrollmentIds.length > 1) {
+            mSession.get().remove(mUserId, 0);
+        } else {
+            mSession.get().remove(mUserId, enrollmentIds[0]);
+        }
+    }
+
+    @Override
+    public void onPointerDown(int pointerId, int x, int y, float minor, float major)
+            throws RemoteException {
+        UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major);
+    }
+
+    @Override
+    public void onPointerUp(int pointerId) throws RemoteException {
+        UdfpsHelper.onFingerUp(mSession.get());
+    }
+
+    @Override
+    public void getAuthenticatorId() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void invalidateAuthenticatorId() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+        mHidlToAidlCallbackConverter.onResetLockout();
+    }
+
+    @Override
+    public void close() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onUiReady() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal detectInteractionWithContext(OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public void onPointerDownWithContext(PointerContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onPointerUpWithContext(PointerContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onContextChanged(OperationContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        //Unsupported in HIDL
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    private class Cancellation extends ICancellationSignal.Stub {
+
+        Cancellation() {}
+        @Override
+        public void cancel() throws RemoteException {
+            try {
+                mSession.get().cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when requesting cancel", e);
+            }
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return 0;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return null;
+        }
+    }
+}
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 a655f360..8bfa560 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
@@ -54,6 +54,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -64,6 +65,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -74,6 +76,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -82,6 +85,8 @@
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -132,6 +137,7 @@
     private final boolean mIsUdfps;
     private final int mSensorId;
     private final boolean mIsPowerbuttonFps;
+    private AidlSession mSession;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -413,6 +419,20 @@
         });
     }
 
+    synchronized AidlSession getSession() {
+        if (mDaemon != null && mSession != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(this::getDaemon,
+                    mCurrentUserId, new AidlResponseHandler(mContext,
+                    mScheduler, mSensorId, mCurrentUserId, new LockoutCache(),
+                    mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> {
+                        mDaemon = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }));
+        }
+    }
+
     @VisibleForTesting
     synchronized IBiometricsFingerprint getDaemon() {
         if (mTestHalEnabled) {
@@ -518,6 +538,10 @@
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
                 if (success) {
+                    if (mCurrentUserId != targetUserId) {
+                        // Create new session with updated user ID
+                        mSession = null;
+                    }
                     mCurrentUserId = targetUserId;
                 } else {
                     Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
@@ -554,47 +578,116 @@
         // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
         // thread.
         mHandler.post(() -> {
-            final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
-                    userId, mContext.getOpPackageName(), sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, mLockoutTracker);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken);
+            } else {
+                scheduleResetLockoutHidl(sensorId, userId);
+            }
         });
     }
 
+    private void scheduleResetLockoutAidl(int sensorId, int userId,
+            @Nullable byte[] hardwareAuthToken) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, hardwareAuthToken, mLockoutTracker,
+                        mLockoutResetDispatcher,
+                        Utils.getCurrentStrength(sensorId));
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleResetLockoutHidl(int sensorId, int userId) {
+        final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
+                userId, mContext.getOpPackageName(), sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN,
+                        mAuthenticationStatsCollector),
+                mBiometricContext, mLockoutTracker);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final FingerprintGenerateChallengeClient client =
-                    new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
-                            new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                            mSensorProperties.sensorId,
-                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                    mAuthenticationStatsCollector),
-                            mBiometricContext);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+            } else {
+                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+            }
         });
     }
 
+    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+        final FingerprintGenerateChallengeClient client =
+                new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
-            final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
-                    mContext, mLazyDaemon, token, userId, opPackageName,
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleRevokeChallengeAidl(userId, token, opPackageName);
+            } else {
+                scheduleRevokeChallengeHidl(userId, token, opPackageName);
+            }
         });
     }
 
+    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient(
+                        mContext, this::getSession,
+                        token, userId, opPackageName,
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, 0L);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
+                mContext, mLazyDaemon, token, userId, opPackageName,
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN,
+                        mAuthenticationStatsCollector),
+                mBiometricContext);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public long scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
@@ -604,40 +697,98 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
-                    userId, hardwareAuthToken, opPackageName,
-                    FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    mBiometricStateCallback.onClientStarted(clientMonitor);
-                }
-
-                @Override
-                public void onBiometricAction(int action) {
-                    mBiometricStateCallback.onBiometricAction(action);
-                }
-
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                    if (success) {
-                        // Update authenticatorIds
-                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
-                                true /* force */);
-                    }
-                }
-            });
+            if (Flags.deHidl()) {
+                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, enrollReason, id);
+            } else {
+                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, enrollReason, id);
+            }
         });
         return id;
     }
 
+    private void scheduleEnrollHidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            @FingerprintManager.EnrollReason int enrollReason, long id) {
+        final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+                mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
+                userId, hardwareAuthToken, opPackageName,
+                FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                mBiometricStateCallback.onClientStarted(clientMonitor);
+            }
+
+            @Override
+            public void onBiometricAction(int action) {
+                mBiometricStateCallback.onBiometricAction(action);
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+                            true /* force */);
+                }
+            }
+        });
+    }
+
+    private void scheduleEnrollAidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            @FingerprintManager.EnrollReason int enrollReason, long id) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
+                        mContext,
+                        this::getSession, token, id,
+                        new ClientMonitorCallbackConverter(receiver),
+                        userId, hardwareAuthToken, opPackageName,
+                        FingerprintUtils.getLegacyInstance(mSensorId),
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, null /* sensorProps */,
+                        mUdfpsOverlayController, mSidefpsController,
+                        mContext.getResources().getInteger(
+                                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
+                        enrollReason);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                mBiometricStateCallback.onClientStarted(clientMonitor);
+            }
+
+            @Override
+            public void onBiometricAction(int action) {
+                mBiometricStateCallback.onBiometricAction(action);
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+                            true /* force */);
+                }
+            }
+        });
+    }
+
     @Override
     public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
@@ -653,17 +804,46 @@
             scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-            final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mLazyDaemon, token, id, listener, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+            if (Flags.deHidl()) {
+                scheduleFingerDetectAidl(token, listener, options, statsClient, id,
+                        isStrongBiometric);
+            } else {
+                scheduleFingerDetectHidl(token, listener, options, statsClient, id,
+                        isStrongBiometric);
+            }
         });
 
         return id;
     }
 
+    private void scheduleFingerDetectHidl(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            int statsClient, long id, boolean isStrongBiometric) {
+        final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+                mLazyDaemon, token, id, listener, options,
+                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                        mAuthenticationStatsCollector),
+                mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleFingerDetectAidl(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            int statsClient, long id, boolean isStrongBiometric) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient(
+                        mContext,
+                        this::getSession, token, id, listener, options,
+                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -674,20 +854,55 @@
             scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-            final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mLazyDaemon, token, requestId, listener, operationId,
-                    restricted, options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, isStrongBiometric,
-                    mTaskStackListener, mLockoutTracker,
-                    mUdfpsOverlayController, mSidefpsController,
-                    allowBackgroundAuthentication, mSensorProperties,
-                    Utils.getCurrentStrength(mSensorId));
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+            if (Flags.deHidl()) {
+                scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            } else {
+                scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            }
         });
     }
 
+    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient(
+                        mContext, this::getSession, token, requestId, listener, operationId,
+                        restricted, options, cookie, false /* requireConfirmation */,
+                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, isStrongBiometric,
+                        mTaskStackListener,
+                        mUdfpsOverlayController, mSidefpsController,
+                        allowBackgroundAuthentication, mSensorProperties, mHandler,
+                        Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+                mContext, mLazyDaemon, token, requestId, listener, operationId,
+                restricted, options, cookie, false /* requireConfirmation */,
+                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                        mAuthenticationStatsCollector),
+                mBiometricContext, isStrongBiometric,
+                mTaskStackListener, mLockoutTracker,
+                mUdfpsOverlayController, mSidefpsController,
+                allowBackgroundAuthentication, mSensorProperties,
+                Utils.getCurrentStrength(mSensorId));
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -719,17 +934,41 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
-                    userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName);
+            } else {
+                scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName);
+            }
         });
     }
 
+    private void scheduleRemoveHidl(@NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+            @NonNull String opPackageName) {
+        final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+                userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleRemoveAidl(@NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId,
+                        opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                        mBiometricContext, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, int userId,
@@ -738,15 +977,11 @@
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
-            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
-                    0 /* fingerprintId */, userId, opPackageName,
-                    FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+            } else {
+                scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+            }
         });
     }
 
@@ -755,17 +990,41 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
-                    mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext,
-                    FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, callback);
+            if (Flags.deHidl()) {
+                scheduleInternalCleanupAidl(userId, callback);
+            } else {
+                scheduleInternalCleanupHidl(userId, callback);
+            }
         });
     }
 
+    private void scheduleInternalCleanupHidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
+                mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext,
+                FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, callback);
+    }
+
+    private void scheduleInternalCleanupAidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext,
+                        FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, callback);
+    }
+
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
new file mode 100644
index 0000000..c3e5cbe
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub {
+
+    final AidlResponseHandler mAidlResponseHandler;
+
+    public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) {
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    @Override
+    public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+        mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining);
+    }
+
+    @Override
+    public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+        onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
+    }
+
+    @Override
+    public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
+        mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+    }
+
+    @Override
+    public void onAuthenticated(long deviceId, int fingerId, int groupId,
+            ArrayList<Byte> token) {
+        if (fingerId != 0) {
+            byte[] hardwareAuthToken = new byte[token.size()];
+            for (int i = 0; i < token.size(); i++) {
+                hardwareAuthToken[i] = token.get(i);
+            }
+            mAidlResponseHandler.onAuthenticationSucceeded(fingerId,
+                    HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+        } else {
+            mAidlResponseHandler.onAuthenticationFailed();
+        }
+    }
+
+    @Override
+    public void onError(long deviceId, int error, int vendorCode) {
+        mAidlResponseHandler.onError(error, vendorCode);
+    }
+
+    @Override
+    public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+        mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+    }
+
+    @Override
+    public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+        mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+    }
+
+    void onChallengeGenerated(long challenge) {
+        mAidlResponseHandler.onChallengeGenerated(challenge);
+    }
+
+    void onChallengeRevoked(long challenge) {
+        mAidlResponseHandler.onChallengeRevoked(challenge);
+    }
+
+    void onResetLockout() {
+        mAidlResponseHandler.onLockoutCleared();
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 36d56c8..0730c67 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -89,7 +89,8 @@
     // Attempt counter should only be cleared when Keyguard goes away or when
     // a biometric is successfully authenticated. Lockout should eventually be done below the HAL.
     // See AuthenticationClient#shouldFrameworkHandleLockout().
-    void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
+    @Override
+    public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
         if (getLockoutModeForUser(userId) != LOCKOUT_NONE) {
             Slog.v(TAG, "Reset biometric lockout for user: " + userId
                     + ", clearAttemptCounter: " + clearAttemptCounter);
@@ -104,7 +105,8 @@
         mLockoutResetCallback.onLockoutReset(userId);
     }
 
-    void addFailedAttemptForUser(int userId) {
+    @Override
+    public void addFailedAttemptForUser(int userId) {
         mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1);
         mTimedLockoutCleared.put(userId, false);
 
@@ -114,7 +116,8 @@
     }
 
     @Override
-    public @LockoutMode int getLockoutModeForUser(int userId) {
+    @LockoutMode
+    public int getLockoutModeForUser(int userId) {
         final int failedAttempts = mFailedAttempts.get(userId, 0);
         if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
             return LOCKOUT_PERMANENT;
@@ -126,6 +129,19 @@
         return LOCKOUT_NONE;
     }
 
+    /**
+     * Clears lockout for Fingerprint HIDL HAL
+     */
+    @Override
+    public void setLockoutModeForUser(int userId, int mode) {
+        mFailedAttempts.put(userId, 0);
+        mTimedLockoutCleared.put(userId, true);
+        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
+        // the alarm might still be pending; remove it.
+        cancelLockoutResetForUser(userId);
+        mLockoutResetCallback.onLockoutReset(userId);
+    }
+
     private void cancelLockoutResetForUser(int userId) {
         mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
     }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 3d347be..f9bc8dc 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -53,6 +53,8 @@
 import android.hardware.usb.UsbManager;
 import android.media.AudioManager;
 import android.nfc.INfcAdapter;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -163,10 +165,6 @@
      * SCALER_ROTATE_AND_CROP_NONE  -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
      */
 
-    // Flags arguments to NFC adapter to enable/disable NFC
-    public static final int DISABLE_POLLING_FLAGS = 0x1000;
-    public static final int ENABLE_POLLING_FLAGS = 0x0000;
-
     // Handler message codes
     private static final int MSG_SWITCH_USER = 1;
     private static final int MSG_NOTIFY_DEVICE_STATE = 2;
@@ -216,7 +214,6 @@
     private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
 
     private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
-    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
     private static final IBinder nfcInterfaceToken = new Binder();
 
     private final boolean mNotifyNfc;
@@ -1274,8 +1271,13 @@
         }
     }
 
-    private void notifyNfcService(boolean enablePolling) {
-
+    // TODO(b/303286040): Remove the raw INfcAdapter usage once |ENABLE_NFC_MAINLINE_FLAG| is
+    // rolled out.
+    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
+    // Flags arguments to NFC adapter to enable/disable NFC
+    public static final int DISABLE_POLLING_FLAGS = 0x1000;
+    public static final int ENABLE_POLLING_FLAGS = 0x0000;
+    private void setNfcReaderModeUsingINfcAdapter(boolean enablePolling) {
         IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME);
         if (nfcServiceBinder == null) {
             Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
@@ -1291,6 +1293,25 @@
         }
     }
 
+    private void notifyNfcService(boolean enablePolling) {
+        if (android.nfc.Flags.enableNfcMainline()) {
+            NfcManager nfcManager = mContext.getSystemService(NfcManager.class);
+            if (nfcManager == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter();
+            if (nfcAdapter == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
+            nfcAdapter.setReaderMode(enablePolling);
+        } else {
+            setNfcReaderModeUsingINfcAdapter(enablePolling);
+        }
+    }
+
     private static int[] toArray(Collection<Integer> c) {
         int len = c.size();
         int[] ret = new int[len];
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 906c66d..76dde54 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1474,11 +1474,11 @@
                                 .getDrawable(R.drawable.ic_safety_protection);
                         toastToShow = Toast.makeCustomToastWithIcon(toastContext,
                                 UiThread.get().getLooper(), message,
-                                Toast.LENGTH_SHORT, safetyProtectionIcon);
+                                Toast.LENGTH_LONG, safetyProtectionIcon);
                     } else {
                         toastToShow = Toast.makeText(
                                 toastContext, UiThread.get().getLooper(), message,
-                                Toast.LENGTH_SHORT);
+                                Toast.LENGTH_LONG);
                     }
                     toastToShow.show();
                 }
diff --git a/services/core/java/com/android/server/compat/overrides/OWNERS b/services/core/java/com/android/server/compat/overrides/OWNERS
index b80f340..6ca7803 100644
--- a/services/core/java/com/android/server/compat/overrides/OWNERS
+++ b/services/core/java/com/android/server/compat/overrides/OWNERS
@@ -1,2 +1,2 @@
-tomnatan@google.com
+mcarli@google.com
 mariiasand@google.com
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 8736a53..ac7d9c1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -221,9 +221,8 @@
 
     /** Flags used when connecting to a sync adapter service */
     private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS =
-            Context.BindServiceFlags.of(
-                    Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE
-                            | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT);
+            Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+                    | Context.BIND_ALLOW_OOM_MANAGEMENT);
 
     /** Singleton instance. */
     @GuardedBy("SyncManager.class")
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 64b17e5..84be521 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -588,8 +588,7 @@
             return wakeLockName;
         }
         return (wakeLockName = target.provider
-                + "/" + target.account.type
-                + "/" + target.account.name);
+                + "/" + target.account.type);
     }
 
     // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
index 3581981..58a654a 100644
--- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
+++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.display.utils.DebugUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -50,7 +51,10 @@
 public class AmbientBrightnessStatsTracker {
 
     private static final String TAG = "AmbientBrightnessStatsTracker";
-    private static final boolean DEBUG = false;
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.AmbientBrightnessStatsTracker DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     @VisibleForTesting
     static final float[] BUCKET_BOUNDARIES_FOR_NEW_STATS =
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index 59844e1..bba5ba3 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -38,6 +38,7 @@
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DebugUtils;
 import com.android.server.display.utils.DeviceConfigParsingUtils;
 
 import java.io.PrintWriter;
@@ -58,8 +59,10 @@
 @Deprecated
 class BrightnessThrottler {
     private static final String TAG = "BrightnessThrottler";
-    private static final boolean DEBUG = false;
 
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.BrightnessThrottler DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
     private static final int THROTTLING_INVALID = -1;
 
     private final Injector mInjector;
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index d550650..ac5dd20 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -64,6 +64,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
+import com.android.server.display.utils.DebugUtils;
 
 import libcore.io.IoUtils;
 
@@ -91,8 +92,10 @@
 public class BrightnessTracker {
 
     static final String TAG = "BrightnessTracker";
-    static final boolean DEBUG = false;
 
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.BrightnessTracker DEBUG && adb reboot'
+    static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
     private static final String EVENTS_FILE = "brightness_events.xml";
     private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml";
     private static final int MAX_EVENTS = 100;
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 0d6635d..3de188f 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -42,6 +42,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.display.utils.DebugUtils;
 import com.android.server.policy.WindowManagerPolicy;
 
 import libcore.io.Streams;
@@ -66,7 +67,9 @@
 final class ColorFade {
     private static final String TAG = "ColorFade";
 
-    private static final boolean DEBUG = false;
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.ColorFade DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     // The layer for the electron beam surface.
     // This is currently hardcoded to be one layer above the boot animation.
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 472c1f5..1ac3a12 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -135,7 +135,8 @@
                             leadDisplayAddress,
                             d.getBrightnessThrottlingMapId(),
                             d.getRefreshRateZoneId(),
-                            d.getRefreshRateThermalThrottlingMapId());
+                            d.getRefreshRateThermalThrottlingMapId(),
+                            d.getPowerThrottlingMapId());
                 }
                 layout.postProcessLocked();
             }
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 4f1df3f..70d4ad2 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,14 +120,14 @@
     }
 
     public static Display.Mode createMode(int width, int height, float refreshRate) {
-        return createMode(width, height, refreshRate, new float[0], new int[0]);
+        return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]);
     }
 
-    public static Display.Mode createMode(int width, int height, float refreshRate,
+    public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate,
             float[] alternativeRefreshRates,
             @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
         return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
-                alternativeRefreshRates, supportedHdrTypes);
+                vsyncRate, alternativeRefreshRates, supportedHdrTypes);
     }
 
     public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af..6695801 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -27,6 +27,8 @@
  * the DisplayBrightnessModeStrategies when updating the brightness.
  */
 public final class DisplayBrightnessState {
+    public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f;
+
     private final float mBrightness;
     private final float mSdrBrightness;
 
@@ -37,6 +39,8 @@
 
     private final boolean mIsSlowChange;
 
+    private final float mCustomAnimationRate;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@
         mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
         mIsSlowChange = builder.isSlowChange();
         mMaxBrightness = builder.getMaxBrightness();
+        mCustomAnimationRate = builder.getCustomAnimationRate();
     }
 
     /**
@@ -97,7 +102,12 @@
         return mMaxBrightness;
     }
 
-
+    /**
+     * @return custom animation rate
+     */
+    public float getCustomAnimationRate() {
+        return mCustomAnimationRate;
+    }
 
     @Override
     public String toString() {
@@ -112,6 +122,7 @@
         stringBuilder.append(getShouldUseAutoBrightness());
         stringBuilder.append("\n    isSlowChange:").append(mIsSlowChange);
         stringBuilder.append("\n    maxBrightness:").append(mMaxBrightness);
+        stringBuilder.append("\n    customAnimationRate:").append(mCustomAnimationRate);
         return stringBuilder.toString();
     }
 
@@ -137,13 +148,14 @@
                         otherState.getDisplayBrightnessStrategyName())
                 && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
                 && mIsSlowChange == otherState.isSlowChange()
-                && mMaxBrightness == otherState.getMaxBrightness();
+                && mMaxBrightness == otherState.getMaxBrightness()
+                && mCustomAnimationRate == otherState.getCustomAnimationRate();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
-                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness);
+                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
     }
 
     /**
@@ -164,6 +176,7 @@
         private boolean mShouldUseAutoBrightness;
         private boolean mIsSlowChange;
         private float mMaxBrightness;
+        private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -180,6 +193,7 @@
             builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
             builder.setIsSlowChange(state.isSlowChange());
             builder.setMaxBrightness(state.getMaxBrightness());
+            builder.setCustomAnimationRate(state.getCustomAnimationRate());
             return builder;
         }
 
@@ -303,6 +317,22 @@
             return mMaxBrightness;
         }
 
+
+        /**
+         * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+         */
+        public Builder setCustomAnimationRate(float animationRate) {
+            this.mCustomAnimationRate = animationRate;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+         */
+        public float getCustomAnimationRate() {
+            return mCustomAnimationRate;
+        }
+
         /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 098cb87..9f4f787 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -23,7 +23,7 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
 import android.util.Slog;
@@ -201,20 +201,6 @@
      * @param state The new display state.
      * @param brightnessState The new display brightnessState.
      * @param sdrBrightnessState The new display brightnessState for SDR layers.
-     * @return A runnable containing work to be deferred until after we have
-     * exited the critical section, or null if none.
-     */
-    public Runnable requestDisplayStateLocked(int state, float brightnessState,
-            float sdrBrightnessState) {
-        return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null);
-    }
-
-    /**
-     * Sets the display state, if supported.
-     *
-     * @param state The new display state.
-     * @param brightnessState The new display brightnessState.
-     * @param sdrBrightnessState The new display brightnessState for SDR layers.
      * @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.
      * @return A runnable containing work to be deferred until after we have exited the critical
      *     section, or null if none.
@@ -223,7 +209,7 @@
             int state,
             float brightnessState,
             float sdrBrightnessState,
-            @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+            @Nullable DisplayOffloadSession displayOffloadSession) {
         return null;
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index cfbe0c6..a0beedb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -77,6 +77,7 @@
 import com.android.server.display.config.ThresholdPoint;
 import com.android.server.display.config.UsiVersion;
 import com.android.server.display.config.XmlParser;
+import com.android.server.display.utils.DebugUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -519,7 +520,10 @@
  */
 public class DisplayDeviceConfig {
     private static final String TAG = "DisplayDeviceConfig";
-    private static final boolean DEBUG = false;
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayDeviceConfig DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN;
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index ea52a3d..67e612d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.display.utils.DebugUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -39,7 +40,10 @@
  */
 class DisplayDeviceRepository implements DisplayAdapter.Listener {
     private static final String TAG = "DisplayDeviceRepository";
-    private static final Boolean DEBUG = false;
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayDeviceRepository DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
     public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d372f30..087cf20 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -160,6 +160,8 @@
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
+import com.android.server.display.utils.DebugUtils;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.utils.FoldSettingProvider;
@@ -224,7 +226,10 @@
  */
 public final class DisplayManagerService extends SystemService {
     private static final String TAG = "DisplayManagerService";
-    private static final boolean DEBUG = false;
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayManagerService DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     // When this system property is set to 0, WFD is forcibly disabled on boot.
     // When this system property is set to 1, WFD is forcibly enabled on boot.
@@ -493,6 +498,7 @@
 
     // If we would like to keep a particular eye on a package, we can set the package name.
     private final boolean mExtraDisplayEventLogging;
+    private final String mExtraDisplayLoggingPackageName;
 
     private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
         @Override
@@ -522,6 +528,8 @@
 
     private final DisplayManagerFlags mFlags;
 
+    private final DisplayNotificationManager mDisplayNotificationManager;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -555,6 +563,7 @@
         mInjector = injector;
         mContext = context;
         mFlags = injector.getFlags();
+        mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext);
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
@@ -580,8 +589,8 @@
         mOverlayProperties = SurfaceControl.getOverlaySupport();
         mSystemReady = false;
         mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
-        final String name = DisplayProperties.debug_vri_package().orElse(null);
-        mExtraDisplayEventLogging = !TextUtils.isEmpty(name);
+        mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
+        mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
     }
 
     public void setupSchedulerPolicies() {
@@ -650,6 +659,7 @@
             }
             mDisplayModeDirector.onBootCompleted();
             mLogicalDisplayMapper.onBootCompleted();
+            mDisplayNotificationManager.onBootCompleted();
         }
     }
 
@@ -752,7 +762,8 @@
 
         mContext.registerReceiver(mIdleModeReceiver, filter);
 
-        mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
+        mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
+                ? SmallAreaDetectionController.create(mContext) : null;
     }
 
     @VisibleForTesting
@@ -784,6 +795,10 @@
         }
     }
 
+    DisplayNotificationManager getDisplayNotificationManager() {
+        return mDisplayNotificationManager;
+    }
+
     private void loadStableDisplayValuesLocked() {
         final Point size = mPersistentDataStore.getStableDisplaySize();
         if (size.x > 0 && size.y > 0) {
@@ -1120,6 +1135,7 @@
                     new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
                             currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
                             overriddenInfo.refreshRateOverride,
+                            currentMode.getVsyncRate(),
                             new float[0], currentMode.getSupportedHdrTypes());
             overriddenInfo.modeId =
                     overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
@@ -1776,7 +1792,8 @@
         synchronized (mSyncRoot) {
             // main display adapter
             registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext,
-                    mHandler, mDisplayDeviceRepo, mFlags));
+                    mHandler, mDisplayDeviceRepo, mFlags,
+                    mDisplayNotificationManager));
 
             // Standalone VR devices rely on a virtual display as their primary display for
             // 2D UI. We register virtual display adapter along side the main display adapter
@@ -2294,8 +2311,10 @@
 
     @GuardedBy("mSyncRoot")
     private boolean hdrConversionIntroducesLatencyLocked() {
+        HdrConversionMode mode = getHdrConversionModeSettingInternal();
         final int preferredHdrOutputType =
-                getHdrConversionModeSettingInternal().getPreferredHdrOutputType();
+                mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
+                        ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
         if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
             int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
             return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
@@ -2576,16 +2595,14 @@
             // TODO(b/202378408) set minimal post-processing only if it's supported once we have a
             // separate API for disabling on-device processing.
             boolean mppRequest = isMinimalPostProcessingAllowed() && preferMinimalPostProcessing;
-            boolean disableHdrConversionForLatency = false;
+            // If HDR conversion introduces latency, disable that in case minimal
+            // post-processing is requested
+            boolean disableHdrConversionForLatency =
+                    mppRequest ? hdrConversionIntroducesLatencyLocked() : false;
 
             if (display.getRequestedMinimalPostProcessingLocked() != mppRequest) {
                 display.setRequestedMinimalPostProcessingLocked(mppRequest);
                 shouldScheduleTraversal = true;
-                // If HDR conversion introduces latency, disable that in case minimal
-                // post-processing is requested
-                if (mppRequest) {
-                    disableHdrConversionForLatency = hdrConversionIntroducesLatencyLocked();
-                }
             }
 
             if (shouldScheduleTraversal) {
@@ -2921,15 +2938,27 @@
     // Send a display event if the display is enabled
     private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
                                                  @DisplayEvent int event) {
+        final boolean displayIsEnabled = display.isEnabledLocked();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER,
+                    "sendDisplayEventLocked#event=" + event + ",displayEnabled="
+                            + displayIsEnabled);
+        }
+
         // Only send updates outside of DisplayManagerService for enabled displays
-        if (display.isEnabledLocked()) {
+        if (displayIsEnabled) {
             sendDisplayEventLocked(display, event);
+        } else if (mExtraDisplayEventLogging) {
+            Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
         }
     }
 
     private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
         int displayId = display.getDisplayIdLocked();
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+        if (mExtraDisplayEventLogging) {
+            Slog.i(TAG, "Deliver Display Event on Handler: " + event);
+        }
         mHandler.sendMessage(msg);
     }
 
@@ -2957,7 +2986,7 @@
 
     // Check if the target app is in cached mode
     private boolean isUidCached(int uid) {
-        if (mActivityManagerInternal == null) {
+        if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) {
             return false;
         }
         int procState = mActivityManagerInternal.getUidProcessState(uid);
@@ -2974,7 +3003,11 @@
                     + displayId + ", event=" + event
                     + (uids != null ? ", uids=" + uids : ""));
         }
-
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER,
+                    "deliverDisplayEvent#event=" + event + ",displayId="
+                            + displayId   + (uids != null ? ", uids=" + uids : ""));
+        }
         // Grab the lock and copy the callbacks.
         final int count;
         synchronized (mSyncRoot) {
@@ -2995,6 +3028,10 @@
                 // For cached apps, save the pending event until it becomes non-cached
                 synchronized (mPendingCallbackSelfLocked) {
                     PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+                    if (extraLogging(callbackRecord.mPackageName)) {
+                        Slog.i(TAG,
+                                "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+                    }
                     if (pendingCallback == null) {
                         mPendingCallbackSelfLocked.put(uid,
                                 new PendingCallback(callbackRecord, displayId, event));
@@ -3009,6 +3046,10 @@
         mTempCallbacks.clear();
     }
 
+    private boolean extraLogging(String packageName) {
+        return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+    }
+
     // Runs on Handler thread.
     // Delivers display group event notifications to callbacks.
     private void deliverDisplayGroupEvent(int groupId, int event) {
@@ -3191,9 +3232,10 @@
 
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                 Handler handler, DisplayAdapter.Listener displayAdapterListener,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags,
+                DisplayNotificationManager displayNotificationManager) {
             return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
-                    flags);
+                    flags, displayNotificationManager);
         }
 
         long getDefaultDisplayDelayTimeout() {
@@ -3451,6 +3493,7 @@
         public final int mUid;
         private final IDisplayManagerCallback mCallback;
         private @EventsMask AtomicLong mEventsMask;
+        private final String mPackageName;
 
         public boolean mWifiDisplayScanRequested;
 
@@ -3460,6 +3503,9 @@
             mUid = uid;
             mCallback = callback;
             mEventsMask = new AtomicLong(eventsMask);
+
+            String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+            mPackageName = packageNames == null ? null : packageNames[0];
         }
 
         public void updateEventsMask(@EventsMask long eventsMask) {
@@ -3468,9 +3514,13 @@
 
         @Override
         public void binderDied() {
-            if (DEBUG) {
+            if (DEBUG || extraLogging(mPackageName)) {
                 Slog.d(TAG, "Display listener for pid " + mPid + " died.");
             }
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                Trace.instant(Trace.TRACE_TAG_POWER,
+                        "displayManagerBinderDied#mPid=" + mPid);
+            }
             onCallbackDied(this);
         }
 
@@ -3479,6 +3529,15 @@
          */
         public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
             if (!shouldSendEvent(event)) {
+                if (extraLogging(mPackageName)) {
+                    Slog.i(TAG,
+                            "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
+                }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                    Trace.instant(Trace.TRACE_TAG_POWER,
+                            "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
+                                    + mEventsMask);
+                }
                 return true;
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9b022d8..8c39d7d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,6 +16,13 @@
 
 package com.android.server.display;
 
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_OVERLAY;
+import static android.view.Display.TYPE_UNKNOWN;
+import static android.view.Display.TYPE_VIRTUAL;
+import static android.view.Display.TYPE_WIFI;
+
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
@@ -26,10 +33,15 @@
 import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
 
 class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
+    private static final String NOTIFICATION_TYPES =
+            "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable";
 
     private final DisplayManagerService mService;
     private final DisplayManagerFlags mFlags;
@@ -46,6 +58,10 @@
         }
         final PrintWriter pw = getOutPrintWriter();
         switch(cmd) {
+            case "show-notification":
+                return showNotification();
+            case "cancel-notifications":
+                return cancelNotifications();
             case "set-brightness":
                 return setBrightness();
             case "reset-brightness-configuration":
@@ -102,6 +118,10 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println();
+        pw.println("  show-notification NOTIFICATION_TYPE");
+        pw.println("    Show notification for one of the following types: " + NOTIFICATION_TYPES);
+        pw.println("  cancel-notifications");
+        pw.println("    Cancel notifications.");
         pw.println("  set-brightness BRIGHTNESS");
         pw.println("    Sets the current brightness to BRIGHTNESS (a number between 0 and 1).");
         pw.println("  reset-brightness-configuration");
@@ -143,9 +163,12 @@
         pw.println("    Sets the user disabled HDR types as TYPES");
         pw.println("  get-user-disabled-hdr-types");
         pw.println("    Returns the user disabled HDR types");
-        pw.println("  get-displays [CATEGORY]");
+        pw.println("  get-displays [-c|--category CATEGORY] [-i|--ids-only] [-t|--type TYPE]");
+        pw.println("    [CATEGORY]");
         pw.println("    Returns the current displays. Can specify string category among");
         pw.println("    DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value.");
+        pw.println("    Can choose to print only the ids of the displays. " +  "Can filter by");
+        pw.println("    display types. For example, '--type external'");
         pw.println("  dock");
         pw.println("    Sets brightness to docked + idle screen brightness mode");
         pw.println("  undock");
@@ -161,14 +184,124 @@
     }
 
     private int getDisplays() {
-        String category = getNextArg();
+        String opt = "", requestedType, category = null;
+        PrintWriter out = getOutPrintWriter();
+
+        List<Integer> displayTypeList = new ArrayList<>();
+        boolean showIdsOnly = false, filterByType = false;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-i":
+                case "--ids-only":
+                    showIdsOnly = true;
+                    break;
+                case "-t":
+                case "--type":
+                    requestedType = getNextArgRequired();
+                    int displayType = getType(requestedType, out);
+                    if (displayType == -1) {
+                        return 1;
+                    }
+                    displayTypeList.add(displayType);
+                    filterByType = true;
+                    break;
+                case "-c":
+                case "--category":
+                    if (category != null) {
+                        out.println("Error: the category has been specified more than one time. "
+                                + "Please select only one category.");
+                        return 1;
+                    }
+                    category = getNextArgRequired();
+                    break;
+                case "":
+                    break;
+                default:
+                    out.println("Error: unknown option '" + opt + "'");
+                    return 1;
+            }
+        }
+
+        String lastCategoryArgument = getNextArg();
+        if (lastCategoryArgument != null) {
+            if (category != null) {
+                out.println("Error: the category has been specified both with the -c option and "
+                        + "the positional argument. Please select only one category.");
+                return 1;
+            }
+            category = lastCategoryArgument;
+        }
+
         DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class);
         Display[] displays = dm.getDisplays(category);
-        PrintWriter out = getOutPrintWriter();
-        out.println("Displays:");
-        for (int i = 0; i < displays.length; i++) {
-            out.println("  " + displays[i]);
+
+        if (filterByType) {
+            displays = Arrays.stream(displays).filter(d -> displayTypeList.contains(d.getType()))
+                    .toArray(Display[]::new);
         }
+
+        if (!showIdsOnly) {
+            out.println("Displays:");
+        }
+        for (int i = 0; i < displays.length; i++) {
+            out.println((showIdsOnly ? displays[i].getDisplayId() : displays[i]));
+        }
+        return 0;
+    }
+
+    private int getType(String type, PrintWriter out) {
+        type = type.toUpperCase(Locale.ENGLISH);
+        switch (type) {
+            case "UNKNOWN":
+                return TYPE_UNKNOWN;
+            case "INTERNAL":
+                return TYPE_INTERNAL;
+            case "EXTERNAL":
+                return TYPE_EXTERNAL;
+            case "WIFI":
+                return TYPE_WIFI;
+            case "OVERLAY":
+                return TYPE_OVERLAY;
+            case "VIRTUAL":
+                return TYPE_VIRTUAL;
+            default:
+                out.println("Error: argument for display type should be "
+                        + "one of 'UNKNOWN', 'INTERNAL', 'EXTERNAL', 'WIFI', 'OVERLAY', 'VIRTUAL', "
+                        + "but got '" + type + "' instead.");
+                return -1;
+        }
+    }
+
+    private int showNotification() {
+        final String notificationType = getNextArg();
+        if (notificationType == null) {
+            getErrPrintWriter().println("Error: no notificationType specified, use one of: "
+                                                + NOTIFICATION_TYPES);
+            return 1;
+        }
+
+        switch(notificationType) {
+            case "on-hotplug-error":
+                mService.getDisplayNotificationManager().onHotplugConnectionError();
+                break;
+            case "on-link-training-failure":
+                mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure();
+                break;
+            case "on-cable-dp-incapable":
+                mService.getDisplayNotificationManager().onCableNotCapableDisplayPort();
+                break;
+            default:
+                getErrPrintWriter().println(
+                        "Error: unexpected notification type=" + notificationType + ", use one of: "
+                                + NOTIFICATION_TYPES);
+                return 1;
+        }
+
+        return 0;
+    }
+
+    private int cancelNotifications() {
+        mService.getDisplayNotificationManager().cancelNotifications();
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ce98559..915f5db 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -79,6 +79,7 @@
 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.utils.DebugUtils;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
@@ -115,7 +116,11 @@
     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 boolean DEBUG = false;
+    private static final String TAG = "DisplayPowerController";
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayPowerController DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
     private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
 
     // If true, uses the color fade on animation.
@@ -608,7 +613,7 @@
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
         mDisplayId = mLogicalDisplay.getDisplayIdLocked();
-        mTag = "DisplayPowerController[" + mDisplayId + "]";
+        mTag = TAG + "[" + mDisplayId + "]";
         mHighBrightnessModeMetadata = hbmMetadata;
         mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
         mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 1652871..fc596dc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -82,6 +82,7 @@
 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;
@@ -118,8 +119,10 @@
     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 boolean DEBUG = false;
-
+    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);
 
     // If true, uses the color fade on animation.
     // We might want to turn this off if we cannot get a guarantee that the screen
@@ -503,10 +506,9 @@
                 mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
                 () -> updatePowerState(), mDisplayId, mSensorManager);
         mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
-        mTag = "DisplayPowerController2[" + mDisplayId + "]";
+        mTag = TAG + "[" + mDisplayId + "]";
         mThermalBrightnessThrottlingDataId =
                 logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
         mDisplayStatsId = mUniqueDisplayId.hashCode();
@@ -562,12 +564,13 @@
                         brightnessSetting, () -> postBrightnessChangeRunnable(),
                         new HandlerExecutor(mHandler));
 
-        mBrightnessClamperController = new BrightnessClamperController(mHandler,
-                modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+        mBrightnessClamperController = mInjector.getBrightnessClamperController(
+                mHandler, modeChangeCallback::run,
+                new BrightnessClamperController.DisplayDeviceData(
                 mUniqueDisplayId,
                 mThermalBrightnessThrottlingDataId,
-                mDisplayDeviceConfig
-        ), mContext);
+                logicalDisplay.getPowerThrottlingDataIdLocked(),
+                mDisplayDeviceConfig), mContext, flags);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -821,10 +824,8 @@
                 .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
         final String thermalBrightnessThrottlingDataId =
                 mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
-        mBrightnessClamperController.onDisplayChanged(
-                new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
-                        mThermalBrightnessThrottlingDataId, config));
+        final String powerThrottlingDataId =
+                mLogicalDisplay.getPowerThrottlingDataIdLocked();
 
         mHandler.postAtTime(() -> {
             boolean changed = false;
@@ -858,6 +859,14 @@
             }
 
             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();
             }
@@ -1348,6 +1357,8 @@
         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.
@@ -1480,6 +1491,9 @@
 
         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) {
@@ -1548,9 +1562,6 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
-            // custom transition duration
-            float customTransitionRate = -1f;
-
             // 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.
@@ -1565,10 +1576,21 @@
                 // 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();
-                customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+                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();
 
@@ -1596,9 +1618,9 @@
                 if (skipAnimation) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
-                } else if (customTransitionRate > 0) {
+                } else if (customAnimationRate > 0) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
-                            customTransitionRate, /* ignoreAnimationLimits = */true);
+                            customAnimationRate, /* ignoreAnimationLimits = */true);
                 } else {
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
@@ -3054,6 +3076,15 @@
                     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,
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2c257a1..be03a80 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,6 +26,8 @@
 import android.view.Choreographer;
 import android.view.Display;
 
+import com.android.server.display.utils.DebugUtils;
+
 import java.io.PrintWriter;
 
 /**
@@ -48,7 +50,9 @@
 final class DisplayPowerState {
     private static final String TAG = "DisplayPowerState";
 
-    private static final boolean DEBUG = false;
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayPowerState DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
     private static String COUNTER_COLOR_FADE = "ColorFadeLevel";
 
     private final Handler mHandler;
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 39172b8..a9f78fd 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -38,6 +38,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
 import com.android.server.display.DisplayManagerService.Clock;
+import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
@@ -54,7 +55,9 @@
 class HighBrightnessModeController {
     private static final String TAG = "HighBrightnessModeController";
 
-    private static final boolean DEBUG = false;
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.HighBrightnessModeController DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     @VisibleForTesting
     static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 3c522e7..0521b8a 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,6 +18,8 @@
 
 import android.util.Slog;
 
+import com.android.server.display.utils.DebugUtils;
+
 import java.io.PrintWriter;
 import java.util.Arrays;
 
@@ -27,7 +29,9 @@
 public class HysteresisLevels {
     private static final String TAG = "HysteresisLevels";
 
-    private static final boolean DEBUG = false;
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     private final float[] mBrighteningThresholdsPercentages;
     private final float[] mDarkeningThresholdsPercentages;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0a1f316..be3207d 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -22,9 +22,8 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
 import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
 import android.hardware.sidekick.SidekickInternal;
 import android.os.Build;
 import android.os.Handler;
@@ -52,6 +51,7 @@
 import com.android.server.LocalServices;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
@@ -86,18 +86,25 @@
 
     private final DisplayManagerFlags mFlags;
 
+    private final DisplayNotificationManager mDisplayNotificationManager;
+
     private Context mOverlayContext;
 
     // Called with SyncRoot lock held.
     LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
-            Handler handler, Listener listener, DisplayManagerFlags flags) {
-        this(syncRoot, context, handler, listener, flags, new Injector());
+            Handler handler, Listener listener, DisplayManagerFlags flags,
+            DisplayNotificationManager displayNotificationManager) {
+        this(syncRoot, context, handler, listener, flags, displayNotificationManager,
+                new Injector());
     }
 
     @VisibleForTesting
     LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
-            Listener listener, DisplayManagerFlags flags, Injector injector) {
+            Listener listener, DisplayManagerFlags flags,
+            DisplayNotificationManager displayNotificationManager,
+            Injector injector) {
         super(syncRoot, context, handler, listener, TAG);
+        mDisplayNotificationManager = displayNotificationManager;
         mInjector = injector;
         mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
         mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport();
@@ -312,10 +319,10 @@
                     SurfaceControl.DisplayMode other = displayModes[j];
                     boolean isAlternative = j != i && other.width == mode.width
                             && other.height == mode.height
-                            && other.refreshRate != mode.refreshRate
+                            && other.peakRefreshRate != mode.peakRefreshRate
                             && other.group == mode.group;
                     if (isAlternative) {
-                        alternativeRefreshRates.add(displayModes[j].refreshRate);
+                        alternativeRefreshRates.add(displayModes[j].peakRefreshRate);
                     }
                 }
                 Collections.sort(alternativeRefreshRates);
@@ -1353,7 +1360,7 @@
 
         DisplayModeRecord(SurfaceControl.DisplayMode mode,
                 float[] alternativeRefreshRates) {
-            mMode = createMode(mode.width, mode.height, mode.refreshRate,
+            mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate,
                     alternativeRefreshRates, mode.supportedHdrTypes);
         }
 
@@ -1368,7 +1375,9 @@
             return mMode.getPhysicalWidth() == mode.width
                     && mMode.getPhysicalHeight() == mode.height
                     && Float.floatToIntBits(mMode.getRefreshRate())
-                    == Float.floatToIntBits(mode.refreshRate);
+                            == Float.floatToIntBits(mode.peakRefreshRate)
+                    && Float.floatToIntBits(mMode.getVsyncRate())
+                            == Float.floatToIntBits(mode.vsyncRate);
         }
 
         public String toString() {
@@ -1454,6 +1463,8 @@
                         + "timestampNanos=" + timestampNanos
                         + ", connectionError=" + connectionError + ")");
             }
+
+            mDisplayNotificationManager.onHotplugConnectionError();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index bd82b81..3d4209e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -190,6 +190,11 @@
     private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;
 
     /**
+     * The ID of the power throttling data that should be used.
+     */
+    private String mPowerThrottlingDataId;
+
+    /**
      * RefreshRateRange limitation for @Temperature.ThrottlingStatus
      */
     @NonNull
@@ -205,6 +210,7 @@
         mIsEnabled = true;
         mIsInTransition = false;
         mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
+        mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
     }
 
@@ -911,6 +917,25 @@
     }
 
     /**
+     * @param powerThrottlingDataId The ID of the brightness throttling data that this
+     *                                  display should use.
+     */
+    public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) {
+        if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) {
+            mPowerThrottlingDataId = powerThrottlingDataId;
+            mDirty = true;
+        }
+    }
+
+    /**
+     * Returns powerThrottlingDataId which is the ID of the brightness
+     * throttling data that this display should use.
+     */
+    public String getPowerThrottlingDataIdLocked() {
+        return mPowerThrottlingDataId;
+    }
+
+    /**
      * Sets the display of which this display is a follower, regarding brightness or other
      * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any
      * others, and has the potential to be a lead display to others.
@@ -976,6 +1001,7 @@
         pw.println("mLeadDisplayId=" + mLeadDisplayId);
         pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);
         pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling);
+        pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index b3b16ad..f3425d2e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -43,6 +43,7 @@
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
+import com.android.server.display.utils.DebugUtils;
 import com.android.server.utils.FoldSettingProvider;
 
 import java.io.PrintWriter;
@@ -63,7 +64,9 @@
 class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
     private static final String TAG = "LogicalDisplayMapper";
 
-    private static final boolean DEBUG = false;
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.LogicalDisplayMapper DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1;
     public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2;
@@ -1123,13 +1126,15 @@
                             displayLayout.getRefreshRateThermalThrottlingMapId()
                     )
             );
-
             setEnabledLocked(newDisplay, displayLayout.isEnabled());
             newDisplay.setThermalBrightnessThrottlingDataIdLocked(
                     displayLayout.getThermalBrightnessThrottlingMapId() == null
                             ? DisplayDeviceConfig.DEFAULT_ID
                             : displayLayout.getThermalBrightnessThrottlingMapId());
-
+            newDisplay.setPowerThrottlingDataIdLocked(
+                    displayLayout.getPowerThrottlingMapId() == null
+                            ? DisplayDeviceConfig.DEFAULT_ID
+                            : displayLayout.getPowerThrottlingMapId());
             newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());
         }
     }
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index cd3a453..3fd58e8 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -35,6 +35,7 @@
 import android.widget.TextView;
 
 import com.android.internal.util.DumpUtils;
+import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
 
@@ -47,8 +48,10 @@
  */
 final class OverlayDisplayWindow implements DumpUtils.Dump {
     private static final String TAG = "OverlayDisplayWindow";
-    private static final boolean DEBUG = false;
 
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.OverlayDisplayWindow DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
     private final float INITIAL_SCALE = 0.5f;
     private final float MIN_SCALE = 0.3f;
     private final float MAX_SCALE = 1.0f;
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
index adaa539..bf384b0 100644
--- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
 import android.util.ArrayMap;
@@ -30,15 +31,14 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.Map;
 
 final class SmallAreaDetectionController {
-    private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
-    private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+    private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds);
+    private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold);
 
     // TODO(b/281720315): Move this to DeviceConfig once server side ready.
     private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
@@ -47,12 +47,8 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final PackageManagerInternal mPackageManager;
-    private final UserManagerInternal mUserManager;
     @GuardedBy("mLock")
     private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
-    // TODO(b/298722189): Update allowlist when user changes
-    @GuardedBy("mLock")
-    private int[] mUserIds;
 
     static SmallAreaDetectionController create(@NonNull Context context) {
         final SmallAreaDetectionController controller =
@@ -67,7 +63,6 @@
     SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
         mContext = context;
         mPackageManager = LocalServices.getService(PackageManagerInternal.class);
-        mUserManager = LocalServices.getService(UserManagerInternal.class);
         deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 BackgroundThread.getExecutor(),
                 new SmallAreaDetectionController.OnPropertiesChangedListener());
@@ -76,6 +71,7 @@
 
     @VisibleForTesting
     void updateAllowlist(@Nullable String property) {
+        final Map<String, Float> allowPkgMap = new ArrayMap<>();
         synchronized (mLock) {
             mAllowPkgMap.clear();
             if (property != null) {
@@ -86,8 +82,11 @@
                         .getStringArray(R.array.config_smallAreaDetectionAllowlist);
                 for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
             }
-            updateSmallAreaDetection();
+
+            if (mAllowPkgMap.isEmpty()) return;
+            allowPkgMap.putAll(mAllowPkgMap);
         }
+        updateSmallAreaDetection(allowPkgMap);
     }
 
     @GuardedBy("mLock")
@@ -105,43 +104,32 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
-        for (int i = 0; i < mUserIds.length; i++) {
-            final int userId = mUserIds[i];
-            final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
-            if (uid > 0) list.put(uid, threshold);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void updateSmallAreaDetection() {
-        if (mAllowPkgMap.isEmpty()) return;
-
-        mUserIds = mUserManager.getUserIds();
-
-        final SparseArray<Float> uidThresholdList = new SparseArray<>();
-        for (String pkg : mAllowPkgMap.keySet()) {
-            final float threshold = mAllowPkgMap.get(pkg);
-            updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+    private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) {
+        final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size());
+        for (String pkg : allowPkgMap.keySet()) {
+            final float threshold = allowPkgMap.get(pkg);
+            final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg);
+            if (stage != null) {
+                appIdThresholdList.put(stage.getAppId(), threshold);
+            }
         }
 
-        final int[] uids = new int[uidThresholdList.size()];
-        final float[] thresholds = new float[uidThresholdList.size()];
-        for (int i = 0; i < uidThresholdList.size();  i++) {
-            uids[i] = uidThresholdList.keyAt(i);
-            thresholds[i] = uidThresholdList.valueAt(i);
+        final int[] appIds = new int[appIdThresholdList.size()];
+        final float[] thresholds = new float[appIdThresholdList.size()];
+        for (int i = 0; i < appIdThresholdList.size();  i++) {
+            appIds[i] = appIdThresholdList.keyAt(i);
+            thresholds[i] = appIdThresholdList.valueAt(i);
         }
-        updateSmallAreaDetection(uids, thresholds);
+        updateSmallAreaDetection(appIds, thresholds);
     }
 
     @VisibleForTesting
-    void updateSmallAreaDetection(int[] uids, float[] thresholds) {
-        nativeUpdateSmallAreaDetection(uids, thresholds);
+    void updateSmallAreaDetection(int[] appIds, float[] thresholds) {
+        nativeUpdateSmallAreaDetection(appIds, thresholds);
     }
 
-    void setSmallAreaDetectionThreshold(int uid, float threshold) {
-        nativeSetSmallAreaDetectionThreshold(uid, threshold);
+    void setSmallAreaDetectionThreshold(int appId, float threshold) {
+        nativeSetSmallAreaDetectionThreshold(appId, threshold);
     }
 
     void dump(PrintWriter pw) {
@@ -151,7 +139,6 @@
             for (String pkg : mAllowPkgMap.keySet()) {
                 pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
             }
-            pw.println("  mUserIds=" + Arrays.toString(mUserIds));
         }
     }
 
@@ -167,11 +154,15 @@
     private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
         @Override
         public void onPackageAdded(@NonNull String packageName, int uid) {
+            float threshold = 0.0f;
             synchronized (mLock) {
                 if (mAllowPkgMap.containsKey(packageName)) {
-                    setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+                    threshold = mAllowPkgMap.get(packageName);
                 }
             }
+            if (threshold > 0.0f) {
+                setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d910e16..b002587 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
@@ -395,7 +396,7 @@
 
         @Override
         public Runnable requestDisplayStateLocked(int state, float brightnessState,
-                float sdrBrightnessState) {
+                float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
             if (state != mDisplayState) {
                 mDisplayState = state;
                 if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index 1e13974..7bc7971 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -21,6 +21,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -41,7 +42,11 @@
     @VisibleForTesting
     static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS;
 
-    private static final boolean DEBUG = false;
+    private static final String TAG = "WakelockController";
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.WakelockController DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     @IntDef(flag = true, prefix = "WAKE_LOCK_", value = {
             WAKE_LOCK_PROXIMITY_POSITIVE,
@@ -100,7 +105,7 @@
     public WakelockController(int displayId,
             DisplayManagerInternal.DisplayPowerCallbacks callbacks) {
         mDisplayId = displayId;
-        mTag = "WakelockController[" + mDisplayId + "]";
+        mTag = TAG + "[" + mDisplayId + "]";
         mDisplayPowerCallbacks = callbacks;
         mSuspendBlockerIdUnfinishedBusiness = "[" + displayId + "]unfinished business";
         mSuspendBlockerIdOnStateChanged = "[" + displayId + "]on state changed";
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index e3d38e7..7660cf8 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -65,7 +66,9 @@
 final class WifiDisplayAdapter extends DisplayAdapter {
     private static final String TAG = "WifiDisplayAdapter";
 
-    private static final boolean DEBUG = false;
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.WifiDisplayAdapter DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     private static final int MSG_SEND_STATUS_CHANGE_BROADCAST = 1;
 
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index 955b8d9..873598a 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -45,6 +45,7 @@
 import android.view.Surface;
 
 import com.android.internal.util.DumpUtils;
+import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
 import java.net.Inet4Address;
@@ -69,7 +70,10 @@
  */
 final class WifiDisplayController implements DumpUtils.Dump {
     private static final String TAG = "WifiDisplayController";
-    private static final boolean DEBUG = false;
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.WifiDisplayController DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
     private static final int DEFAULT_CONTROL_PORT = 7236;
     private static final int MAX_THROUGHPUT = 50;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 54a280f..dfcda40 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.os.PowerManager;
 
+import com.android.server.display.DisplayBrightnessState;
+
 import java.io.PrintWriter;
 
 /**
@@ -33,6 +35,10 @@
         return mBrightnessCap;
     }
 
+    float getCustomAnimationRate() {
+        return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+    }
+
     boolean isActive() {
         return mIsActive;
     }
@@ -52,7 +58,8 @@
 
     abstract void stop();
 
-    enum Type {
-        THERMAL
+    protected enum Type {
+        THERMAL,
+        POWER
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 14637af..b574919 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -34,9 +34,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -52,25 +55,28 @@
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
     private final ClamperChangeListener mClamperChangeListenerExternal;
-
     private final Executor mExecutor;
     private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
 
     private final List<BrightnessModifier> mModifiers;
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
+    private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
     @Nullable
     private Type mClamperType = null;
     private boolean mClamperApplied = false;
 
     public BrightnessClamperController(Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
-        this(new Injector(), handler, clamperChangeListener, data, context);
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data,  Context context,
+            DisplayManagerFlags flags) {
+        this(new Injector(), handler, clamperChangeListener, data, context, flags);
     }
 
     @VisibleForTesting
     BrightnessClamperController(Injector injector, Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data,  Context context,
+            DisplayManagerFlags flags) {
         mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler = handler;
         mClamperChangeListenerExternal = clamperChangeListener;
@@ -84,7 +90,7 @@
             }
         };
 
-        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data);
+        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);
         mModifiers = injector.getModifiers(context);
         mOnPropertiesChangedListener =
                 properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -110,6 +116,7 @@
         builder.setIsSlowChange(slowChange);
         builder.setBrightness(cappedBrightness);
         builder.setMaxBrightness(mBrightnessCap);
+        builder.setCustomAnimationRate(mCustomAnimationRate);
 
         if (mClamperType != null) {
             builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -144,6 +151,8 @@
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
         } else if (mClamperType == Type.THERMAL) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+        } else if (mClamperType == Type.POWER) {
+            return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
         } else {
             Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -177,6 +186,7 @@
     private void recalculateBrightnessCap() {
         float brightnessCap = PowerManager.BRIGHTNESS_MAX;
         Type clamperType = null;
+        float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
 
         BrightnessClamper<?> minClamper = mClampers.stream()
                 .filter(BrightnessClamper::isActive)
@@ -186,13 +196,17 @@
         if (minClamper != null) {
             brightnessCap = minClamper.getBrightnessCap();
             clamperType = minClamper.getType();
+            customAnimationRate = minClamper.getCustomAnimationRate();
         }
 
-        if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+        if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+                || mCustomAnimationRate != customAnimationRate) {
             mBrightnessCap = brightnessCap;
             mClamperType = clamperType;
+            mCustomAnimationRate = customAnimationRate;
             mClamperChangeListenerExternal.onChanged();
         }
+
     }
 
     private void start() {
@@ -219,10 +233,15 @@
         }
 
         List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
-                ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+                ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
+                DisplayManagerFlags flags) {
             List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
             clampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListener, data));
+            if (flags.isPowerThrottlingClamperEnabled()) {
+                clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
+                            data));
+            }
             return clampers;
         }
 
@@ -235,21 +254,26 @@
     }
 
     /**
-     * Data for clampers
+     * Config Data for clampers
      */
-    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
+                BrightnessPowerClamper.PowerData {
         @NonNull
         private final String mUniqueDisplayId;
         @NonNull
         private final String mThermalThrottlingDataId;
+        @NonNull
+        private final String mPowerThrottlingDataId;
 
         private final DisplayDeviceConfig mDisplayDeviceConfig;
 
         public DisplayDeviceData(@NonNull String uniqueDisplayId,
                 @NonNull String thermalThrottlingDataId,
+                @NonNull String powerThrottlingDataId,
                 @NonNull DisplayDeviceConfig displayDeviceConfig) {
             mUniqueDisplayId = uniqueDisplayId;
             mThermalThrottlingDataId = thermalThrottlingDataId;
+            mPowerThrottlingDataId = powerThrottlingDataId;
             mDisplayDeviceConfig = displayDeviceConfig;
         }
 
@@ -272,5 +296,24 @@
             return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
                     mThermalThrottlingDataId);
         }
+
+        @NonNull
+        @Override
+        public String getPowerThrottlingDataId() {
+            return mPowerThrottlingDataId;
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingData getPowerThrottlingData() {
+            return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get(
+                    mPowerThrottlingDataId);
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+            return mDisplayDeviceConfig.getPowerThrottlingConfigData();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
new file mode 100644
index 0000000..339b589
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.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.display.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessPowerClamper extends
+        BrightnessClamper<BrightnessPowerClamper.PowerData> {
+
+    private static final String TAG = "BrightnessPowerClamper";
+    @NonNull
+    private final Injector mInjector;
+    @NonNull
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final ClamperChangeListener mChangeListener;
+    @Nullable
+    private PmicMonitor mPmicMonitor;
+    // data from DeviceConfig, for all displays, for all dataSets
+    // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
+    @NonNull
+    private Map<String, Map<String, PowerThrottlingData>>
+            mPowerThrottlingDataOverride = Map.of();
+    // data from DisplayDeviceConfig, for particular display+dataSet
+    @Nullable
+    private PowerThrottlingData mPowerThrottlingDataFromDDC = null;
+    // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId,
+    // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC.
+    @Nullable
+    private PowerThrottlingData mPowerThrottlingDataActive = null;
+    @Nullable
+    private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
+
+    private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
+    private float mCurrentAvgPowerConsumed = 0;
+    @Nullable
+    private String mUniqueDisplayId = null;
+    @Nullable
+    private String mDataId = null;
+
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float powerQuota = Float.parseFloat(value);
+            return new ThrottlingLevel(status, powerQuota);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, PowerThrottlingData>
+            mDataSetMapper = PowerThrottlingData::create;
+
+
+    BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
+            PowerData powerData) {
+        this(new Injector(), handler, listener, powerData);
+    }
+
+    @VisibleForTesting
+    BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
+            PowerData powerData) {
+        mInjector = injector;
+        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler = handler;
+        mChangeListener = listener;
+
+        mHandler.post(() -> {
+            setDisplayData(powerData);
+            loadOverrideData();
+            start();
+        });
+
+    }
+
+    @Override
+    @NonNull
+    BrightnessClamper.Type getType() {
+        return Type.POWER;
+    }
+
+    @Override
+    void onDeviceConfigChanged() {
+        mHandler.post(() -> {
+            loadOverrideData();
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void onDisplayChanged(PowerData data) {
+        mHandler.post(() -> {
+            setDisplayData(data);
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void stop() {
+        if (mPmicMonitor != null) {
+            mPmicMonitor.shutdown();
+        }
+    }
+
+    /**
+     * Dumps the state of BrightnessPowerClamper.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("BrightnessPowerClamper:");
+        pw.println("  mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+        pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
+        pw.println("  mCurrentThermalLevel=" + mCurrentThermalLevel);
+        pw.println("  mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+                : mPowerThrottlingDataFromDDC.toString()));
+        super.dump(pw);
+    }
+
+    private void recalculateActiveData() {
+        if (mUniqueDisplayId == null || mDataId == null) {
+            return;
+        }
+        mPowerThrottlingDataActive = mPowerThrottlingDataOverride
+                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+                        mPowerThrottlingDataFromDDC);
+        if (mPowerThrottlingDataActive != null) {
+            if (mPmicMonitor != null) {
+                mPmicMonitor.stop();
+                mPmicMonitor.start();
+            }
+        } else {
+            if (mPmicMonitor != null) {
+                mPmicMonitor.stop();
+            }
+        }
+        recalculateBrightnessCap();
+    }
+
+    private void loadOverrideData() {
+        String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData();
+        mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+                throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+    }
+
+    private void setDisplayData(@NonNull PowerData data) {
+        mUniqueDisplayId = data.getUniqueDisplayId();
+        mDataId = data.getPowerThrottlingDataId();
+        mPowerThrottlingDataFromDDC = data.getPowerThrottlingData();
+        if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) {
+            Slog.wtf(TAG,
+                    "Power throttling data is missing for powerThrottlingDataId=" + mDataId);
+        }
+
+        mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
+        if (mPowerThrottlingConfigData == null) {
+            Slog.d(TAG,
+                    "Power throttling data is missing for configuration data.");
+        }
+    }
+
+    private void recalculateBrightnessCap() {
+        boolean isActive = false;
+        float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+        float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
+        if (mPowerThrottlingDataActive == null) {
+            return;
+        }
+        if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) {
+            isActive = true;
+            // calculate new brightness Cap.
+            // Brightness has a linear relation to power-consumed.
+            targetBrightnessCap =
+                    (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX;
+            // Cap to lowest allowed brightness on device.
+            targetBrightnessCap = Math.max(targetBrightnessCap,
+                    mPowerThrottlingConfigData.brightnessLowestCapAllowed);
+        }
+
+        if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
+            mIsActive = isActive;
+            mBrightnessCap = targetBrightnessCap;
+            mChangeListener.onChanged();
+        }
+    }
+
+    private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) {
+        float powerQuota = 0f;
+        if (mPowerThrottlingDataActive != null) {
+            // Throttling levels are sorted by increasing severity
+            for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) {
+                if (level.thermalStatus <= thermalStatus) {
+                    powerQuota = level.powerQuotaMilliWatts;
+                } else {
+                    // Throttling levels that are greater than the current status are irrelevant
+                    break;
+                }
+            }
+        }
+        return powerQuota;
+    }
+
+    private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
+        mHandler.post(() -> {
+            mCurrentThermalLevel = thermalStatus;
+            mCurrentAvgPowerConsumed = avgPowerConsumed;
+            recalculateBrightnessCap();
+        });
+    }
+
+    private void start() {
+        if (mPowerThrottlingConfigData == null) {
+            return;
+        }
+        PowerChangeListener listener = (powerConsumed, thermalStatus) -> {
+            recalculatePowerQuotaChange(powerConsumed, thermalStatus);
+        };
+        mPmicMonitor =
+            mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis);
+        mPmicMonitor.start();
+    }
+
+    public interface PowerData {
+        @NonNull
+        String getUniqueDisplayId();
+
+        @NonNull
+        String getPowerThrottlingDataId();
+
+        @Nullable
+        PowerThrottlingData getPowerThrottlingData();
+
+        @Nullable
+        PowerThrottlingConfigData getPowerThrottlingConfigData();
+    }
+
+    /**
+     * Power change listener
+     */
+    @FunctionalInterface
+    public interface PowerChangeListener {
+        /**
+         * Notifies that power state changed from power controller.
+         */
+        void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus);
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) {
+            return new PmicMonitor(listener, pollingTime);
+        }
+
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 39f0b13..200d88a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,7 +24,6 @@
 import android.view.SurfaceControlHdrLayerInfoListener;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessUtils;
 import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
@@ -176,21 +175,14 @@
         } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
             mDesiredMaxBrightness = expectedMaxBrightness;
             long debounceTime;
-            long transitionDuration;
             if (mDesiredMaxBrightness > mMaxBrightness) {
                 debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
-                transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
             } else {
                 debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
-                transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
             }
 
-            float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
-            float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
-
-            mDesiredTransitionRate = Math.abs(
-                    (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
-
             mHandler.removeCallbacks(mDebouncer);
             mHandler.postDelayed(mDebouncer, debounceTime);
         }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
new file mode 100644
index 0000000..26784f23
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.IThermalService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.power.PowerStatsInternal;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Monitors the display consumed power and helps make informed decision,
+ * regarding overconsumption.
+ */
+public class PmicMonitor {
+    private static final String TAG = "PmicMonitor";
+
+    // The executor to periodically monitor the display power.
+    private final ScheduledExecutorService mExecutor;
+    @NonNull
+    private final PowerChangeListener mPowerChangeListener;
+    private final long mPowerMonitorPeriodConfigSecs;
+    private final PowerStatsInternal mPowerStatsInternal;
+    @VisibleForTesting final IThermalService mThermalService;
+    private ScheduledFuture<?> mPmicMonitorFuture;
+    private float mLastEnergyConsumed = 0;
+    private float mCurrentAvgPower = 0;
+    private Temperature mCurrentTemperature;
+    private long mCurrentTimestampMillis = 0;
+
+    PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) {
+        mPowerChangeListener = listener;
+        mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+        mThermalService = IThermalService.Stub.asInterface(
+                ServiceManager.getService(Context.THERMAL_SERVICE));
+        // start a periodic worker thread.
+        mExecutor = Executors.newSingleThreadScheduledExecutor();
+        mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs;
+    }
+
+    @Nullable
+    private Temperature getDisplayTemperature() {
+        Temperature retTemperature = null;
+        try {
+            Temperature[] temperatures;
+            // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
+            temperatures = mThermalService.getCurrentTemperaturesWithType(
+                        Temperature.TYPE_SKIN);
+            if (temperatures.length > 1) {
+                Slog.w(TAG, "Multiple skin temperatures not allowed!");
+            }
+            if (temperatures.length > 0) {
+                retTemperature = temperatures[0];
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "getDisplayTemperature failed" + e);
+        }
+        return retTemperature;
+    }
+
+    private void capturePeriodicDisplayPower() {
+        final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
+        if (energyConsumers == null || energyConsumers.length == 0) {
+            return;
+        }
+        final IntArray energyConsumerIds = new IntArray();
+        for (int i = 0; i < energyConsumers.length; i++) {
+            if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) {
+                energyConsumerIds.add(energyConsumers[i].id);
+            }
+        }
+
+        if (energyConsumerIds.size() == 0) {
+            Slog.w(TAG, "DISPLAY energyConsumerIds size is null");
+            return;
+        }
+        CompletableFuture<EnergyConsumerResult[]> futureECRs =
+                mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray());
+        if (futureECRs == null) {
+            Slog.w(TAG, "Energy consumers results are null");
+            return;
+        }
+
+        EnergyConsumerResult[] displayResults;
+        try {
+            displayResults = futureECRs.get();
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e);
+            displayResults = null;
+        } catch (ExecutionException e) {
+            Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e);
+            displayResults = null;
+        }
+
+        if (displayResults == null || displayResults.length == 0) {
+            Slog.w(TAG, "displayResults are null");
+            return;
+        }
+        // Support for only 1 display rail.
+        float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed);
+        float timeIntervalSeconds =
+                (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f;
+        // energy consumed is received in microwatts-seconds.
+        float currentPower = energyConsumed / timeIntervalSeconds;
+        // convert power received in microwatts to milliwatts.
+        currentPower = currentPower / 1000.f;
+
+        // capture thermal state.
+        Temperature displayTemperature = getDisplayTemperature();
+        mCurrentAvgPower = currentPower;
+        mCurrentTemperature = displayTemperature;
+        mLastEnergyConsumed = displayResults[0].energyUWs;
+        mCurrentTimestampMillis = displayResults[0].timestampMs;
+        if (mCurrentTemperature != null) {
+            mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+        }
+    }
+
+    /**
+    * Start polling the power IC.
+    */
+    public void start() {
+        if (mPowerStatsInternal == null) {
+            Slog.w(TAG, "Power stats service not found for monitoring.");
+            return;
+        }
+        if (mThermalService == null) {
+            Slog.w(TAG, "Thermal service not found.");
+            return;
+        }
+        if (mPmicMonitorFuture == null) {
+            mPmicMonitorFuture = mExecutor.scheduleAtFixedRate(
+                                    this::capturePeriodicDisplayPower,
+                                    mPowerMonitorPeriodConfigSecs,
+                                    mPowerMonitorPeriodConfigSecs,
+                                    TimeUnit.SECONDS);
+        } else {
+            Slog.e(TAG, "already scheduled, stop() called before start.");
+        }
+    }
+
+    /**
+     * Stop polling to power IC.
+     */
+    public void stop() {
+        if (mPmicMonitorFuture != null) {
+            mPmicMonitorFuture.cancel(true);
+            mPmicMonitorFuture = null;
+        }
+    }
+
+    /**
+     * Shutdown power IC service and worker thread.
+     */
+    public void shutdown() {
+        mExecutor.shutdownNow();
+    }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index d8831fa..e3aa161 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -78,6 +78,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
@@ -148,10 +149,12 @@
             1f, 1f, 1f, 1f
     };
 
+    private final DisplayManagerFlags mDisplayManagerFlags = new DisplayManagerFlags();
+
     @VisibleForTesting
     final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController =
             new DisplayWhiteBalanceTintController(
-                    LocalServices.getService(DisplayManagerInternal.class));
+                    LocalServices.getService(DisplayManagerInternal.class), mDisplayManagerFlags);
     private final NightDisplayTintController mNightDisplayTintController =
             new NightDisplayTintController();
     private final TintController mGlobalSaturationTintController =
@@ -231,7 +234,9 @@
         }
     }
 
-    @VisibleForTesting void onUserChanged(int userHandle) {
+    // should be called in handler thread (same thread that started animation)
+    @VisibleForTesting
+    void onUserChanged(int userHandle) {
         final ContentResolver cr = getContext().getContentResolver();
 
         if (mCurrentUser != UserHandle.USER_NULL) {
@@ -470,6 +475,15 @@
         }
     }
 
+    // should be called in handler thread (same thread that started animation)
+    @VisibleForTesting
+    void cancelAllAnimators() {
+        mNightDisplayTintController.cancelAnimator();
+        mGlobalSaturationTintController.cancelAnimator();
+        mReduceBrightColorsTintController.cancelAnimator();
+        mDisplayWhiteBalanceTintController.cancelAnimator();
+    }
+
     private boolean resetReduceBrightColors() {
         if (mCurrentUser == UserHandle.USER_NULL) {
             return false;
@@ -716,15 +730,16 @@
                         tintController.computeMatrixForCct(to));
                 tintController.setAppliedCct(to);
             } else {
+                final long duration = tintController.getTransitionDurationMilliseconds(to > from);
                 Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct="
-                        + to + " fromCct=" + from);
+                        + to + " fromCct=" + from + " with duration=" + duration);
                 ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
                 tintController.setAnimator(valueAnimator);
                 final CctEvaluator evaluator = tintController.getEvaluator();
                 if (evaluator != null) {
                     valueAnimator.setEvaluator(evaluator);
                 }
-                valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
+                valueAnimator.setDuration(duration);
                 valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
                         getContext(), android.R.interpolator.linear));
                 valueAnimator.addUpdateListener((ValueAnimator animator) -> {
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index bf0139f..b9fd1d0 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 
@@ -65,6 +66,8 @@
     boolean mSetUp = false;
     private final float[] mMatrixDisplayWhiteBalance = new float[16];
     private long mTransitionDuration;
+    private long mTransitionDurationIncrease;
+    private long mTransitionDurationDecrease;
     private Boolean mIsAvailable;
     // This feature becomes disallowed if the device is in an unsupported strong/light state.
     private boolean mIsAllowed = true;
@@ -74,8 +77,12 @@
 
     private final DisplayManagerInternal mDisplayManagerInternal;
 
-    DisplayWhiteBalanceTintController(DisplayManagerInternal dm) {
+    private final DisplayManagerFlags mDisplayManagerFlags;
+
+    DisplayWhiteBalanceTintController(DisplayManagerInternal dm,
+            DisplayManagerFlags displayManagerFlags) {
         mDisplayManagerInternal = dm;
+        mDisplayManagerFlags = displayManagerFlags;
     }
 
     @Override
@@ -139,6 +146,16 @@
         mTransitionDuration = res.getInteger(
                 R.integer.config_displayWhiteBalanceTransitionTime);
 
+        if (!mDisplayManagerFlags.isAdaptiveTone2Enabled()) {
+            mTransitionDurationDecrease = mTransitionDuration;
+            mTransitionDurationIncrease = mTransitionDuration;
+        } else {
+            mTransitionDurationIncrease = res.getInteger(
+                    R.integer.config_displayWhiteBalanceTransitionTimeIncrease);
+            mTransitionDurationDecrease = res.getInteger(
+                    R.integer.config_displayWhiteBalanceTransitionTimeDecrease);
+        }
+
         int[] cctRangeMinimums = res.getIntArray(
                 R.array.config_displayWhiteBalanceDisplayRangeMinimums);
         int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
@@ -323,6 +340,11 @@
     }
 
     @Override
+    public long getTransitionDurationMilliseconds(boolean isIncreasing) {
+        return isIncreasing ? mTransitionDurationIncrease : mTransitionDurationDecrease;
+    }
+
+    @Override
     public void dump(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("    mSetUp = " + mSetUp);
@@ -348,6 +370,9 @@
             pw.println("    mMatrixDisplayWhiteBalance = "
                     + matrixToString(mMatrixDisplayWhiteBalance, 4));
             pw.println("    mIsAllowed = " + mIsAllowed);
+            pw.println("    mTransitionDuration = " + mTransitionDuration);
+            pw.println("    mTransitionDurationIncrease = " + mTransitionDurationIncrease);
+            pw.println("    mTransitionDurationDecrease = " + mTransitionDurationDecrease);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 384333a..716661d 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -75,6 +75,10 @@
         return TRANSITION_DURATION;
     }
 
+    public long getTransitionDurationMilliseconds(boolean direction) {
+        return TRANSITION_DURATION;
+    }
+
     /**
      * Dump debug information.
      */
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 48d671d..837fbf7 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -40,9 +40,9 @@
     public final long mBrightnessIncreaseDebounceMillis;
 
     /**
-     * Brightness increase animation duration
+     * Brightness increase animation speed
      */
-    public final long mBrightnessIncreaseDurationMillis;
+    public final float mScreenBrightnessRampIncrease;
 
     /**
      * Debounce time for brightness decrease
@@ -50,19 +50,19 @@
     public final long mBrightnessDecreaseDebounceMillis;
 
     /**
-     * Brightness decrease animation duration
+     * Brightness decrease animation speed
      */
-    public final long mBrightnessDecreaseDurationMillis;
+    public final float mScreenBrightnessRampDecrease;
 
     @VisibleForTesting
     public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
-            long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
-            long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+            long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
+            long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
         mMaxBrightnessLimits = maxBrightnessLimits;
         mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
-        mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+        mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
         mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
-        mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+        mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
     }
 
     @Override
@@ -70,9 +70,9 @@
         return "HdrBrightnessData {"
                 + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
                 + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
-                + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+                + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
                 + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
-                + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+                + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
                 + "} ";
     }
 
@@ -94,8 +94,8 @@
 
         return new HdrBrightnessData(brightnessLimits,
                 hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
-                hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+                hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
                 hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
-                hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+                hdrConfig.getScreenBrightnessRampDecrease().floatValue());
     }
 }
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 23ffe59..465584c 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -77,6 +77,12 @@
     // Test parameters
     // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
 
+    // allows to customize power throttling data
+    public String getPowerThrottlingData() {
+        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null);
+    }
+
     // allows to customize brightness throttling data
     public String getBrightnessThrottlingData() {
         return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
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 f7b9869..7f3ea6a 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -21,6 +21,7 @@
 import android.util.Slog;
 
 import com.android.server.display.feature.flags.Flags;
+import com.android.server.display.utils.DebugUtils;
 
 import java.util.function.Supplier;
 
@@ -28,9 +29,13 @@
  * Utility class to read the flags used in the display manager server.
  */
 public class DisplayManagerFlags {
-    private static final boolean DEBUG = false;
     private static final String TAG = "DisplayManagerFlags";
 
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.DisplayManagerFlags DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
+
     private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
             Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
             Flags::enableConnectedDisplayManagement);
@@ -47,6 +52,10 @@
             Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1,
             Flags::enableAdaptiveToneImprovements1);
 
+    private final FlagState mAdaptiveToneImprovements2 = new FlagState(
+            Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_2,
+            Flags::enableAdaptiveToneImprovements2);
+
     private final FlagState mDisplayOffloadFlagState = new FlagState(
             Flags.FLAG_ENABLE_DISPLAY_OFFLOAD,
             Flags::enableDisplayOffload);
@@ -55,24 +64,43 @@
             Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
             Flags::enableModeLimitForExternalDisplay);
 
+    private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
+            Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
+            Flags::enableConnectedDisplayErrorHandling);
+
     private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState(
             Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE,
             Flags::backUpSmoothDisplayAndForcePeakRefreshRate);
 
+    private final FlagState mPowerThrottlingClamperFlagState = new FlagState(
+            Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
+            Flags::enablePowerThrottlingClamper);
+
+    private final FlagState mSmallAreaDetectionFlagState = new FlagState(
+            Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
+            Flags::enableSmallAreaDetection);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
     }
 
-    /** Returns whether hdr clamper is enabled on not*/
+    /** Returns whether NBM Controller is enabled or not. */
     public boolean isNbmControllerEnabled() {
         return mNbmControllerFlagState.isEnabled();
     }
 
+    /** Returns whether hdr clamper is enabled on not. */
     public boolean isHdrClamperEnabled() {
         return mHdrClamperFlagState.isEnabled();
     }
 
+    /** Returns whether power throttling clamper is enabled on not. */
+    public boolean isPowerThrottlingClamperEnabled() {
+        return mPowerThrottlingClamperFlagState.isEnabled();
+    }
+
+
     /**
      * Returns whether adaptive tone improvements are enabled
      */
@@ -80,6 +108,13 @@
         return mAdaptiveToneImprovements1.isEnabled();
     }
 
+    /**
+     * Returns whether adaptive tone improvements are enabled
+     */
+    public boolean isAdaptiveTone2Enabled() {
+        return mAdaptiveToneImprovements2.isEnabled();
+    }
+
     /** Returns whether resolution range voting feature is enabled or not. */
     public boolean isDisplayResolutionRangeVotingEnabled() {
         return isExternalDisplayLimitModeEnabled();
@@ -112,10 +147,19 @@
         return mDisplayOffloadFlagState.isEnabled();
     }
 
+    /** Returns whether error notifications for connected displays are enabled on not */
+    public boolean isConnectedDisplayErrorHandlingEnabled() {
+        return mConnectedDisplayErrorHandlingFlagState.isEnabled();
+    }
+
     public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() {
         return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
     }
 
+    public boolean isSmallAreaDetectionEnabled() {
+        return mSmallAreaDetectionFlagState.isEnabled();
+    }
+
     private static class FlagState {
 
         private final String mName;
@@ -129,7 +173,6 @@
             mFlagFunction = flagFunction;
         }
 
-        // TODO(b/297159910): Simplify using READ-ONLY flags when available.
         private boolean isEnabled() {
             if (mEnabledSet) {
                 if (DEBUG) {
@@ -146,14 +189,7 @@
         }
 
         private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
-            boolean flagValue = false;
-            try {
-                flagValue = flagFunction.get();
-            } catch (Throwable ex) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
-                }
-            }
+            boolean flagValue = flagFunction.get();
             // TODO(b/299462337) Remove when the infrastructure is ready.
             if (Build.IS_ENG || Build.IS_USERDEBUG) {
                 return SystemProperties.getBoolean("persist.sys." + flagName + "-override",
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 c24b3ca..9141814 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
@@ -27,6 +27,14 @@
 }
 
 flag {
+    name: "enable_power_throttling_clamper"
+    namespace: "display_manager"
+    description: "Feature flag for Power Throttling Clamper"
+    bug: "294777007"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_adaptive_tone_improvements_1"
     namespace: "display_manager"
     description: "Feature flag for Adaptive Tone Improvements"
@@ -35,6 +43,14 @@
 }
 
 flag {
+    name: "enable_adaptive_tone_improvements_2"
+    namespace: "display_manager"
+    description: "Feature flag for Further Adaptive Tone Improvements"
+    bug: "294762632"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_display_resolution_range_voting"
     namespace: "display_manager"
     description: "Feature flag to enable voting for ranges of resolutions"
@@ -74,9 +90,26 @@
 }
 
 flag {
+    name: "enable_connected_display_error_handling"
+    namespace: "display_manager"
+    description: "Feature flag for connected display error handling"
+    bug: "283461472"
+    is_fixed_read_only: true
+}
+
+flag {
     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"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_small_area_detection"
+    namespace: "display_manager"
+    description: "Feature flag for SmallAreaDetection"
+    bug: "298722189"
+    is_fixed_read_only: true
+}
+
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index d9ec3de..40cb3303 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -80,7 +80,8 @@
         createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
                 DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
                 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ null);
     }
 
     /**
@@ -97,6 +98,7 @@
      * @param refreshRateZoneId Layout limited refresh rate zone name.
      * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
      *                                          policy should be used.
+     * @param powerThrottlingMapId Name of which power throttling policy should be used.
      *
      * @exception IllegalArgumentException When a default display owns a display group other than
      *            DEFAULT_DISPLAY_GROUP.
@@ -106,7 +108,8 @@
             String displayGroupName, DisplayIdProducer idProducer, int position,
             @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
             @Nullable String refreshRateZoneId,
-            @Nullable String refreshRateThermalThrottlingMapId) {
+            @Nullable String refreshRateThermalThrottlingMapId,
+            @Nullable String powerThrottlingMapId) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
             return;
@@ -139,7 +142,7 @@
 
         final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
                 brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
-                refreshRateThermalThrottlingMapId);
+                refreshRateThermalThrottlingMapId, powerThrottlingMapId);
 
         mDisplays.add(display);
     }
@@ -311,6 +314,9 @@
         @Nullable
         private final String mThermalRefreshRateThrottlingMapId;
 
+        @Nullable
+        private final String mPowerThrottlingMapId;
+
         // The ID of the lead display that this display will follow in a layout. -1 means no lead.
         // This is determined using {@code mLeadDisplayAddress}.
         private int mLeadDisplayId;
@@ -318,7 +324,8 @@
         private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
                 @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
-                @Nullable String refreshRateThermalThrottlingMapId) {
+                @Nullable String refreshRateThermalThrottlingMapId,
+                @Nullable String powerThrottlingMapId) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
             mIsEnabled = isEnabled;
@@ -328,6 +335,7 @@
             mLeadDisplayAddress = leadDisplayAddress;
             mRefreshRateZoneId = refreshRateZoneId;
             mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
+            mPowerThrottlingMapId = powerThrottlingMapId;
             mLeadDisplayId = NO_LEAD_DISPLAY;
         }
 
@@ -344,6 +352,7 @@
                     + ", mLeadDisplayId: " + mLeadDisplayId
                     + ", mLeadDisplayAddress: " + mLeadDisplayAddress
                     + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+                    + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId
                     + "}";
         }
 
@@ -366,7 +375,9 @@
                     && this.mLeadDisplayId == otherDisplay.mLeadDisplayId
                     && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
                     && Objects.equals(mThermalRefreshRateThrottlingMapId,
-                    otherDisplay.mThermalRefreshRateThrottlingMapId);
+                    otherDisplay.mThermalRefreshRateThrottlingMapId)
+                    && Objects.equals(mPowerThrottlingMapId,
+                    otherDisplay.mPowerThrottlingMapId);
         }
 
         @Override
@@ -382,6 +393,7 @@
             result = 31 * result + mLeadDisplayId;
             result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
             result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
+            result = 31 * result + Objects.hashCode(mPowerThrottlingMapId);
             return result;
         }
 
@@ -441,6 +453,15 @@
             return mThermalRefreshRateThrottlingMapId;
         }
 
+        /**
+         * Gets the id of the power throttling map that should be used.
+         * @return The ID of the power throttling map that this display should use,
+         *         null if unspecified, will fall back to default.
+         */
+        public String getPowerThrottlingMapId() {
+            return mPowerThrottlingMapId;
+        }
+
         private void setLeadDisplayId(int id) {
             mLeadDisplayId = id;
         }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index ca23844..d023913 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -723,7 +723,9 @@
             if (mode.getPhysicalWidth() > maxAllowedWidth
                     || mode.getPhysicalHeight() > maxAllowedHeight
                     || mode.getPhysicalWidth() < outSummary.minWidth
-                    || mode.getPhysicalHeight() < outSummary.minHeight) {
+                    || mode.getPhysicalHeight() < outSummary.minHeight
+                    || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate
+                    || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
new file mode 100644
index 0000000..f683e81
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.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.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Detects usb issues related to an external display connected.
+ */
+public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener {
+    private static final String TAG = "ConnectedDisplayUsbErrorsDetector";
+
+    /**
+     * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}.
+     */
+    public interface Injector {
+
+        /**
+         * @return {@link UsbManager} service.
+         */
+        UsbManager getUsbManager();
+    }
+
+    /**
+     * USB errors listener
+     */
+    public interface Listener {
+
+        /**
+         * Link training failure callback.
+         */
+        void onDisplayPortLinkTrainingFailure();
+
+        /**
+         * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort.
+         */
+        void onCableNotCapableDisplayPort();
+    }
+
+    private Listener mListener;
+    private final Injector mInjector;
+    private final Context mContext;
+    private final boolean mIsConnectedDisplayErrorHandlingEnabled;
+
+    ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+            @NonNull final Context context) {
+        this(flags, context, () -> context.getSystemService(UsbManager.class));
+    }
+
+    @VisibleForTesting
+    ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+            @NonNull final Context context, @NonNull final Injector injector) {
+        mContext = context;
+        mInjector = injector;
+        mIsConnectedDisplayErrorHandlingEnabled =
+                flags.isConnectedDisplayErrorHandlingEnabled();
+    }
+
+    /** Register listener for usb error events. */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    void registerListener(final Listener listener) {
+        if (!mIsConnectedDisplayErrorHandlingEnabled) {
+            return;
+        }
+
+        final var usbManager = mInjector.getUsbManager();
+        if (usbManager == null) {
+            Slog.e(TAG, "UsbManager is null");
+            return;
+        }
+
+        mListener = listener;
+
+        try {
+            usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this);
+        } catch (IllegalStateException e) {
+            Slog.e(TAG, "Failed to register listener", e);
+        }
+    }
+
+    /**
+     * Callback upon changes in {@link DisplayPortAltModeInfo}.
+     * @param portId    String describing the {@link android.hardware.usb.UsbPort} that was changed.
+     * @param info      New {@link DisplayPortAltModeInfo} for the corresponding portId.
+     */
+    @Override
+    public void onDisplayPortAltModeInfoChanged(@NonNull String portId,
+            @NonNull DisplayPortAltModeInfo info) {
+        if (mListener == null) {
+            return;
+        }
+
+        if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus()
+                && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus()
+        ) {
+            mListener.onCableNotCapableDisplayPort();
+            return;
+        }
+
+        if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) {
+            mListener.onDisplayPortLinkTrainingFailure();
+            return;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
new file mode 100644
index 0000000..5cdef38
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.notifications;
+
+import static android.app.Notification.COLOR_DEFAULT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Manages notifications for {@link com.android.server.display.DisplayManagerService}.
+ */
+public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener {
+    /** Dependency injection interface for {@link DisplayNotificationManager} */
+    public interface Injector {
+        /** Get {@link NotificationManager} service or null if not available. */
+        @Nullable
+        NotificationManager getNotificationManager();
+
+        /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */
+        @Nullable
+        ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector();
+    }
+
+    private static final String TAG = "DisplayNotificationManager";
+    private static final String NOTIFICATION_GROUP_NAME = TAG;
+    private static final String DISPLAY_NOTIFICATION_TAG = TAG;
+    private static final int DISPLAY_NOTIFICATION_ID = 1;
+    private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L;
+
+    private final Injector mInjector;
+    private final Context mContext;
+    private final boolean mConnectedDisplayErrorHandlingEnabled;
+    private NotificationManager mNotificationManager;
+    private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector;
+
+    public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) {
+        this(flags, context, new Injector() {
+            @Nullable
+            @Override
+            public NotificationManager getNotificationManager() {
+                return context.getSystemService(NotificationManager.class);
+            }
+
+            @Nullable
+            @Override
+            public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() {
+                return new ConnectedDisplayUsbErrorsDetector(flags, context);
+            }
+        });
+    }
+
+    @VisibleForTesting
+    DisplayNotificationManager(final DisplayManagerFlags flags, final Context context,
+            final Injector injector) {
+        mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled();
+        mContext = context;
+        mInjector = injector;
+    }
+
+    /**
+     * Initialize services, which may be not yet published during boot.
+     * see {@link android.os.ServiceManager.ServiceNotFoundException}.
+     */
+    public void onBootCompleted() {
+        mNotificationManager = mInjector.getNotificationManager();
+        if (mNotificationManager == null) {
+            Slog.e(TAG, "onBootCompleted: NotificationManager is null");
+            return;
+        }
+
+        mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector();
+        if (mConnectedDisplayUsbErrorsDetector != null) {
+            mConnectedDisplayUsbErrorsDetector.registerListener(this);
+        }
+    }
+
+    /**
+     * Display error notification upon DisplayPort link training failure.
+     */
+    @Override
+    public void onDisplayPortLinkTrainingFailure() {
+        if (!mConnectedDisplayErrorHandlingEnabled) {
+            Slog.d(TAG, "onDisplayPortLinkTrainingFailure:"
+                                + " mConnectedDisplayErrorHandlingEnabled is false");
+            return;
+        }
+
+        sendErrorNotification(createErrorNotification(
+                R.string.connected_display_unavailable_notification_title,
+                R.string.connected_display_unavailable_notification_content));
+    }
+
+    /**
+     * Display error notification upon cable not capable of DisplayPort connected to a device
+     * capable of DisplayPort.
+     */
+    @Override
+    public void onCableNotCapableDisplayPort() {
+        if (!mConnectedDisplayErrorHandlingEnabled) {
+            Slog.d(TAG, "onCableNotCapableDisplayPort:"
+                                + " mConnectedDisplayErrorHandlingEnabled is false");
+            return;
+        }
+
+        sendErrorNotification(createErrorNotification(
+                R.string.connected_display_cable_dont_support_displays_notification_title,
+                R.string.connected_display_cable_dont_support_displays_notification_content));
+    }
+
+    /**
+     * Send notification about hotplug connection error.
+     */
+    public void onHotplugConnectionError() {
+        if (!mConnectedDisplayErrorHandlingEnabled) {
+            Slog.d(TAG, "onHotplugConnectionError:"
+                                + " mConnectedDisplayErrorHandlingEnabled is false");
+            return;
+        }
+
+        sendErrorNotification(createErrorNotification(
+                R.string.connected_display_unavailable_notification_title,
+                R.string.connected_display_unavailable_notification_content));
+    }
+
+    /**
+     * Cancel sent notifications.
+     */
+    public void cancelNotifications() {
+        if (mNotificationManager == null) {
+            Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null");
+            return;
+        }
+
+        mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID);
+    }
+
+    /**
+     * Send generic error notification.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void sendErrorNotification(final Notification notification) {
+        if (mNotificationManager == null) {
+            Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null");
+            return;
+        }
+
+        mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID,
+                notification);
+    }
+
+    /**
+     * @return a newly built notification about an issue with connected display.
+     */
+    private Notification createErrorNotification(final int titleId, final int messageId) {
+        final Resources resources = mContext.getResources();
+        final CharSequence title = resources.getText(titleId);
+        final CharSequence message = resources.getText(messageId);
+
+        int color = COLOR_DEFAULT;
+        try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) {
+            color = attrs.getColor(0, color);
+        } catch (Resources.NotFoundException e) {
+            Slog.e(TAG, "colorError attribute is not found: " + e.getMessage());
+        }
+
+        return new Notification.Builder(mContext, ALERTS)
+                .setGroup(NOTIFICATION_GROUP_NAME)
+                .setSmallIcon(R.drawable.usb_cable_unknown_issue)
+                .setWhen(0)
+                .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC)
+                .setOngoing(false)
+                .setTicker(title)
+                .setColor(color)
+                .setContentTitle(title)
+                .setContentText(message)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setCategory(Notification.CATEGORY_ERROR)
+                .build();
+    }
+}
diff --git a/services/core/java/com/android/server/display/utils/DebugUtils.java b/services/core/java/com/android/server/display/utils/DebugUtils.java
new file mode 100644
index 0000000..1496495
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/DebugUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import android.util.Log;
+
+public class DebugUtils {
+
+    public static final boolean DEBUG_ALL = Log.isLoggable("DisplayManager_All", Log.DEBUG);
+
+    /**
+     * Returns whether the specified tag has logging enabled. Use the tag name specified in the
+     * calling class, or DisplayManager_All to globally enable all tags in display.
+     * To enable:
+     * adb shell setprop persist.log.tag.DisplayManager_All DEBUG
+     * To disable:
+     * adb shell setprop persist.log.tag.DisplayManager_All \"\"
+     */
+    public static boolean isDebuggable(String tag) {
+        return Log.isLoggable(tag, Log.DEBUG) || DEBUG_ALL;
+    }
+}
diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp
new file mode 100644
index 0000000..067288d
--- /dev/null
+++ b/services/core/java/com/android/server/feature/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "dropbox_flags",
+    package: "com.android.server.feature.flags",
+    srcs: [
+        "dropbox_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "dropbox_flags_lib",
+    aconfig_declarations: "dropbox_flags",
+}
diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
new file mode 100644
index 0000000..fee4bf3
--- /dev/null
+++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.feature.flags"
+
+flag{
+    name: "enable_read_dropbox_permission"
+    namespace: "preload_safety"
+    description: "Feature flag for permission to Read dropbox data"
+    bug: "287512663"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index c6a50ed..7b844a0 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -48,6 +48,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
+import com.android.text.flags.Flags;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -240,21 +241,35 @@
         mContext = context;
         mIsSafeMode = safeMode;
 
-        SystemServerInitThreadPool.submit(() -> {
-            initialize();
+        if (Flags.useOptimizedBoottimeFontLoading()) {
+            Slog.i(TAG, "Using optimized boot-time font loading.");
+            SystemServerInitThreadPool.submit(() -> {
+                initialize();
 
-            // Set system font map only if there is updatable font directory.
-            // If there is no updatable font directory, `initialize` will have already loaded the
-            // system font map, so there's no need to set the system font map again here.
-            if  (mUpdatableFontDir != null) {
-                try {
-                    Typeface.setSystemFontMap(getCurrentFontMap());
-                } catch (IOException | ErrnoException e) {
-                    Slog.w(TAG, "Failed to set system font map of system_server");
+                // Set system font map only if there is updatable font directory.
+                // If there is no updatable font directory, `initialize` will have already loaded
+                // the system font map, so there's no need to set the system font map again here.
+                synchronized (mUpdatableFontDirLock) {
+                    if  (mUpdatableFontDir != null) {
+                        setSystemFontMap();
+                    }
                 }
-            }
+                serviceStarted.complete(null);
+            }, "FontManagerService_create");
+        } else {
+            Slog.i(TAG, "Not using optimized boot-time font loading.");
+            initialize();
+            setSystemFontMap();
             serviceStarted.complete(null);
-        }, "FontManagerService_create");
+        }
+    }
+
+    private void setSystemFontMap() {
+        try {
+            Typeface.setSystemFontMap(getCurrentFontMap());
+        } catch (IOException | ErrnoException e) {
+            Slog.w(TAG, "Failed to set system font map of system_server");
+        }
     }
 
     @Nullable
@@ -291,9 +306,11 @@
         synchronized (mUpdatableFontDirLock) {
             mUpdatableFontDir = createUpdatableFontDir();
             if (mUpdatableFontDir == null) {
-                // If fs-verity is not supported, load preinstalled system font map and use it for
-                // all apps.
-                Typeface.loadPreinstalledSystemFontMap();
+                if (Flags.useOptimizedBoottimeFontLoading()) {
+                    // If fs-verity is not supported, load preinstalled system font map and use it
+                    // for all apps.
+                    Typeface.loadPreinstalledSystemFontMap();
+                }
                 setSerializedFontMap(serializeSystemServerFontMap());
                 return;
             }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 429db5e..c28a68b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4951,6 +4951,11 @@
         AudioDeviceAttributes attributes = new AudioDeviceAttributes(
                 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "",
                 new ArrayList<AudioProfile>(), audioDescriptors);
+        // Set SAM to ON whenever CEC is disabled. Failure to do so may result in the absence
+        // of sound when CEC is disabled and eARC is enabled due to SAM being in the off state.
+        if (!isCecControlEnabled()) {
+            setSystemAudioActivated(true);
+        }
         getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index bad6bf0..8580b96 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -1246,11 +1246,17 @@
                         isFirstConfiguration);
         for (int i = 0; i < imeInfoList.size(); i++) {
             KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
-            boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
-            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
-                    noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
-                    noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
-                            : layoutInfo.mSelectionCriteria);
+            String layoutName = null;
+            int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT;
+            if (layoutInfo != null && layoutInfo.mDescriptor != null) {
+                layoutSelectionCriteria = layoutInfo.mSelectionCriteria;
+                KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor);
+                if (d != null) {
+                    layoutName = d.keyboardLayoutName;
+                }
+            }
+            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, layoutName,
+                    layoutSelectionCriteria);
         }
         KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
     }
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 08e5977..2dd2a16 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -491,7 +491,7 @@
             private final InputDevice mInputDevice;
             private boolean mIsFirstConfiguration;
             private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>();
-            private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>();
+            private final List<String> mSelectedLayoutList = new ArrayList<>();
             private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>();
 
             public Builder(@NonNull InputDevice inputDevice) {
@@ -511,7 +511,7 @@
              * Adds keyboard layout configuration info for a particular IME subtype language
              */
             public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype,
-                    @Nullable KeyboardLayout selectedLayout,
+                    @Nullable String selectedLayout,
                     @LayoutSelectionCriteria int layoutSelectionCriteria) {
                 Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null");
                 if (!isValidSelectionCriteria(layoutSelectionCriteria)) {
@@ -533,7 +533,6 @@
                 }
                 List<LayoutConfiguration> configurationList = new ArrayList<>();
                 for (int i = 0; i < size; i++) {
-                    KeyboardLayout selectedLayout = mSelectedLayoutList.get(i);
                     @LayoutSelectionCriteria int layoutSelectionCriteria =
                             mLayoutSelectionCriteriaList.get(i);
                     InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
@@ -552,9 +551,9 @@
                             imeSubtype.getPhysicalKeyboardHintLayoutType());
 
                     // Sanitize null values
-                    String keyboardLayoutName =
-                            selectedLayout == null ? DEFAULT_LAYOUT_NAME
-                                    : selectedLayout.getLabel();
+                    String keyboardLayoutName = mSelectedLayoutList.get(i) == null
+                            ? DEFAULT_LAYOUT_NAME
+                            : mSelectedLayoutList.get(i);
 
                     configurationList.add(
                             new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index b090334..27e1b9a 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -60,84 +60,99 @@
     private static final long TIMEOUT_MS = 10_000;
 
     /** Handler for registering timeouts for live entries. */
+    @GuardedBy("mLock")
     private final Handler mHandler;
 
     /** Singleton instance of the History. */
-    @GuardedBy("ImeTrackerService.this")
+    @GuardedBy("mLock")
     private final History mHistory = new History();
 
+    private final Object mLock = new Object();
+
     ImeTrackerService(@NonNull Looper looper) {
         mHandler = new Handler(looper, null /* callback */, true /* async */);
     }
 
     @NonNull
     @Override
-    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+    public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final var binder = new Binder();
         final var token = new ImeTracker.Token(binder, tag);
         final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
                 origin, reason);
-        mHistory.addEntry(binder, entry);
+        synchronized (mLock) {
+            mHistory.addEntry(binder, entry);
 
-        // Register a delayed task to handle the case where the new entry times out.
-        mHandler.postDelayed(() -> {
-            synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
-            }
-        }, TIMEOUT_MS);
-
+            // Register a delayed task to handle the case where the new entry times out.
+            mHandler.postDelayed(() -> {
+                synchronized (mLock) {
+                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+                            ImeTracker.PHASE_NOT_SET);
+                }
+            }, TIMEOUT_MS);
+        }
         return token;
     }
 
     @NonNull
     @Override
-    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+    public ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final var binder = new Binder();
         final var token = new ImeTracker.Token(binder, tag);
         final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
                 origin, reason);
-        mHistory.addEntry(binder, entry);
+        synchronized (mLock) {
+            mHistory.addEntry(binder, entry);
 
-        // Register a delayed task to handle the case where the new entry times out.
-        mHandler.postDelayed(() -> {
-            synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
-            }
-        }, TIMEOUT_MS);
-
+            // Register a delayed task to handle the case where the new entry times out.
+            mHandler.postDelayed(() -> {
+                synchronized (mLock) {
+                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+                            ImeTracker.PHASE_NOT_SET);
+                }
+            }, TIMEOUT_MS);
+        }
         return token;
     }
 
     @Override
-    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
-        final var entry = mHistory.getEntry(binder);
-        if (entry == null) return;
+    public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            final var entry = mHistory.getEntry(binder);
+            if (entry == null) return;
 
-        entry.mPhase = phase;
+            entry.mPhase = phase;
+        }
     }
 
     @Override
-    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
-            @ImeTracker.Phase int phase) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+    public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+        }
     }
 
     @Override
-    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
-            @ImeTracker.Phase int phase) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+    public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+        }
     }
 
     @Override
-    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    public void onShown(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
     }
 
     @Override
-    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    public void onHidden(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
     }
 
     /**
@@ -146,25 +161,30 @@
      * @param statsToken the token corresponding to the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
-    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
+    public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
             @NonNull String requestWindowName) {
-        final var entry = mHistory.getEntry(statsToken.getBinder());
-        if (entry == null) return;
+        synchronized (mLock) {
+            final var entry = mHistory.getEntry(statsToken.getBinder());
+            if (entry == null) return;
 
-        entry.mRequestWindowName = requestWindowName;
+            entry.mRequestWindowName = requestWindowName;
+        }
     }
 
     /** Dumps the contents of the history. */
-    public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-        mHistory.dump(pw, prefix);
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        synchronized (mLock) {
+            mHistory.dump(pw, prefix);
+        }
     }
 
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     @Override
-    public synchronized boolean hasPendingImeVisibilityRequests() {
+    public boolean hasPendingImeVisibilityRequests() {
         super.hasPendingImeVisibilityRequests_enforcePermission();
-
-        return !mHistory.mLiveEntries.isEmpty();
+        synchronized (mLock) {
+            return !mHistory.mLiveEntries.isEmpty();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3435e56..0c4ecbc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5529,6 +5529,42 @@
         return canAccess;
     }
 
+    @GuardedBy("ImfLock.class")
+    private void switchKeyboardLayoutLocked(int direction) {
+        final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+        if (currentImi == null) {
+            return;
+        }
+        final InputMethodSubtypeHandle currentSubtypeHandle =
+                InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
+        final InputMethodSubtypeHandle nextSubtypeHandle =
+                mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
+                        direction > 0);
+        if (nextSubtypeHandle == null) {
+            return;
+        }
+        final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+        if (nextImi == null) {
+            return;
+        }
+
+        final int subtypeCount = nextImi.getSubtypeCount();
+        if (subtypeCount == 0) {
+            if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
+                setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+            }
+            return;
+        }
+
+        for (int i = 0; i < subtypeCount; ++i) {
+            if (nextSubtypeHandle.equals(
+                    InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
+                setInputMethodLocked(nextImi.getId(), i);
+                return;
+            }
+        }
+    }
+
     private void publishLocalService() {
         LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
     }
@@ -5734,38 +5770,7 @@
         @Override
         public void switchKeyboardLayout(int direction) {
             synchronized (ImfLock.class) {
-                final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
-                if (currentImi == null) {
-                    return;
-                }
-                final InputMethodSubtypeHandle currentSubtypeHandle =
-                        InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
-                final InputMethodSubtypeHandle nextSubtypeHandle =
-                        mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
-                                direction > 0);
-                if (nextSubtypeHandle == null) {
-                    return;
-                }
-                final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
-                if (nextImi == null) {
-                    return;
-                }
-
-                final int subtypeCount = nextImi.getSubtypeCount();
-                if (subtypeCount == 0) {
-                    if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
-                        setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
-                    }
-                    return;
-                }
-
-                for (int i = 0; i < subtypeCount; ++i) {
-                    if (nextSubtypeHandle.equals(
-                            InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
-                        setInputMethodLocked(nextImi.getId(), i);
-                        return;
-                    }
-                }
+                switchKeyboardLayoutLocked(direction);
             }
         }
 
@@ -6767,5 +6772,21 @@
         public void resetStylusHandwriting(int requestId) {
             mImms.resetStylusHandwriting(requestId);
         }
+
+        @BinderThread
+        @Override
+        public void switchKeyboardLayoutAsync(int direction) {
+            synchronized (ImfLock.class) {
+                if (!mImms.calledWithValidTokenLocked(mToken)) {
+                    return;
+                }
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mImms.switchKeyboardLayoutLocked(direction);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index d4578dc..4851a81 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -486,9 +486,12 @@
                 Settings.Secure.DEFAULT_INPUT_METHOD,
                 userId);
         if (!TextUtils.isEmpty(currentInputMethod)) {
-            String inputMethodPkgName = ComponentName
-                    .unflattenFromString(currentInputMethod)
-                    .getPackageName();
+            ComponentName componentName = ComponentName.unflattenFromString(currentInputMethod);
+            if (componentName == null) {
+                Slog.d(TAG, "inValid input method");
+                return false;
+            }
+            String inputMethodPkgName = componentName.getPackageName();
             int inputMethodUid = getPackageUid(inputMethodPkgName, userId);
             return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(),
                     inputMethodUid);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 74b7f08..323cdc5 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -474,13 +474,14 @@
                 // If we have a GNSS provider override, add the hardware provider as a standalone
                 // option for use by apps with the correct permission. Note the GNSS HAL can only
                 // support a single client, so mGnssManagerService.getGnssLocationProvider() can
-                // only be installed with a single provider.
+                // only be installed with a single provider. Locations from this provider won't
+                // be reported through the passive provider.
                 LocationProviderManager gnssHardwareManager =
                         new LocationProviderManager(
                                 mContext,
                                 mInjector,
                                 GPS_HARDWARE_PROVIDER,
-                                mPassiveManager,
+                                /*passiveManager=*/ null,
                                 Collections.singletonList(Manifest.permission.LOCATION_HARDWARE));
                 addLocationProviderManager(
                         gnssHardwareManager, mGnssManagerService.getGnssLocationProvider());
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f168f43..568618e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -240,6 +240,10 @@
     private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts";
     private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
+    private static final String MIGRATED_FRP2 = "migrated_frp2";
+    private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
+    private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
+
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
     // ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
@@ -693,9 +697,9 @@
             return;
         }
 
-        if (isUserKeyUnlocked(userId)) {
-            // If storage is not locked, the user will be automatically unlocked so there is
-            // no need to show the notification.
+        if (isCeStorageUnlocked(userId)) {
+            // If the user's CE storage is already unlocked, then the user will be automatically
+            // unlocked, so there is no need to show the notification.
             return;
         }
 
@@ -909,14 +913,14 @@
     }
 
     private void migrateOldData() {
-        if (getString("migrated_keystore_namespace", null, 0) == null) {
+        if (getString(MIGRATED_KEYSTORE_NS, null, 0) == null) {
             boolean success = true;
             synchronized (mSpManager) {
                 success &= mSpManager.migrateKeyNamespace();
             }
             success &= migrateProfileLockKeys();
             if (success) {
-                setString("migrated_keystore_namespace", "true", 0);
+                setString(MIGRATED_KEYSTORE_NS, "true", 0);
                 Slog.i(TAG, "Migrated keys to LSS namespace");
             } else {
                 Slog.w(TAG, "Failed to migrate keys to LSS namespace");
@@ -936,9 +940,9 @@
         // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
         // where it had run before.
         if (LockPatternUtils.frpCredentialEnabled(mContext)
-                && !getBoolean("migrated_frp2", false, 0)) {
+                && !getBoolean(MIGRATED_FRP2, false, 0)) {
             migrateFrpCredential();
-            setBoolean("migrated_frp2", true, 0);
+            setBoolean(MIGRATED_FRP2, true, 0);
         }
     }
 
@@ -1026,16 +1030,16 @@
             // they did have an SP then their CE key wasn't encrypted by it.
             //
             // If this gets interrupted (e.g. by the device powering off), there shouldn't be a
-            // problem since this will run again on the next boot, and setUserKeyProtection() is
-            // okay with the key being already protected by the given secret.
-            if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
+            // problem since this will run again on the next boot, and setCeStorageProtection() is
+            // okay with the CE key being already protected by the given secret.
+            if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
                 for (UserInfo user : mUserManager.getAliveUsers()) {
                     removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
                     synchronized (mSpManager) {
                         migrateUserToSpWithBoundCeKeyLocked(user.id);
                     }
                 }
-                setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
+                setString(MIGRATED_SP_CE_ONLY, "true", 0);
             }
 
             mThirdPartyAppsStarted = true;
@@ -1062,7 +1066,7 @@
                 Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                 return;
             }
-            setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
+            setCeStorageProtection(userId, result.syntheticPassword);
         }
     }
 
@@ -1347,8 +1351,8 @@
         AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
     }
 
-    private void unlockKeystore(byte[] password, int userHandle) {
-        Authorization.onLockScreenEvent(false, userHandle, password, null);
+    private void unlockKeystore(int userId, SyntheticPassword sp) {
+        Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
@@ -2001,10 +2005,11 @@
         mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
     }
 
-    private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
+    private void setCeStorageProtection(@UserIdInt int userId, SyntheticPassword sp) {
+        final byte[] secret = sp.deriveFileBasedEncryptionKey();
         final long callingId = Binder.clearCallingIdentity();
         try {
-            mStorageManager.setUserKeyProtection(userId, secret);
+            mStorageManager.setCeStorageProtection(userId, secret);
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to protect CE key for user " + userId, e);
         } finally {
@@ -2012,11 +2017,11 @@
         }
     }
 
-    private boolean isUserKeyUnlocked(int userId) {
+    private boolean isCeStorageUnlocked(int userId) {
         try {
-            return mStorageManager.isUserKeyUnlocked(userId);
+            return mStorageManager.isCeStorageUnlocked(userId);
         } catch (RemoteException e) {
-            Slog.e(TAG, "failed to check user key locked state", e);
+            Slog.e(TAG, "Error checking whether CE storage is unlocked", e);
             return false;
         }
     }
@@ -2027,8 +2032,8 @@
      * This method doesn't throw exceptions because it is called opportunistically whenever a user
      * is started.  Whether it worked or not can be detected by whether the key got unlocked or not.
      */
-    private void unlockUserKey(@UserIdInt int userId, SyntheticPassword sp) {
-        if (isUserKeyUnlocked(userId)) {
+    private void unlockCeStorage(@UserIdInt int userId, SyntheticPassword sp) {
+        if (isCeStorageUnlocked(userId)) {
             Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
             return;
         }
@@ -2036,7 +2041,7 @@
         final String userType = isUserSecure(userId) ? "secured" : "unsecured";
         final byte[] secret = sp.deriveFileBasedEncryptionKey();
         try {
-            mStorageManager.unlockUserKey(userId, userInfo.serialNumber, secret);
+            mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, 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);
@@ -2045,10 +2050,14 @@
         }
     }
 
-    private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+    @Override
+    public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+        checkPasswordReadPermission();
         synchronized (mSpManager) {
-            if (isUserKeyUnlocked(userId)) {
+            if (isCeStorageUnlocked(userId)) {
                 Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
+                // This method actually does more than unlock CE storage.  However, if CE storage is
+                // already unlocked, then the other parts must have already been done too.
                 return;
             }
             if (isUserSecure(userId)) {
@@ -2065,7 +2074,7 @@
                 return;
             }
             onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
-            unlockUserKey(userId, result.syntheticPassword);
+            unlockCeStorage(userId, result.syntheticPassword);
         }
     }
 
@@ -2768,7 +2777,7 @@
             final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                     LockscreenCredential.createNone(), sp, userId);
             setCurrentLskfBasedProtectorId(protectorId, userId);
-            setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+            setCeStorageProtection(userId, sp);
             onSyntheticPasswordCreated(userId, sp);
             Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
             return sp;
@@ -2827,9 +2836,9 @@
             }
         }
 
-        unlockKeystore(sp.deriveKeyStorePassword(), userId);
+        unlockKeystore(userId, sp);
 
-        unlockUserKey(userId, sp);
+        unlockCeStorage(userId, sp);
 
         unlockUser(userId);
 
@@ -2893,8 +2902,8 @@
 
             mSpManager.clearSidForUser(userId);
             gateKeeperClearSecureUserId(userId);
-            unlockUserKey(userId, sp);
-            unlockKeystore(sp.deriveKeyStorePassword(), userId);
+            unlockCeStorage(userId, sp);
+            unlockKeystore(userId, sp);
             setKeystorePassword(null, userId);
             removeBiometricsForUser(userId);
         }
@@ -3454,11 +3463,6 @@
         }
 
         @Override
-        public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
-            LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
-        }
-
-        @Override
         public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
             LockSettingsService.this.createNewUser(userId, userSerialNumber);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index df95c69..4bac872 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -174,7 +174,7 @@
             pw.println("    Sets the lock screen as PIN, using the given PIN to unlock.");
             pw.println("");
             pw.println("  set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>");
-            pw.println("    Sets the lock screen as password, using the given PASSOWRD to unlock.");
+            pw.println("    Sets the lock screen as password, using the given PASSWORD to unlock.");
             pw.println("");
             pw.println("  clear [--old <CREDENTIAL>] [--user USER_ID]");
             pw.println("    Clears the lock credentials.");
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1c5ecb7..1e8b387 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -45,7 +45,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
index b9c9bae..8cb334d 100644
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -23,6 +23,8 @@
 import android.media.AudioDeviceInfo;
 import android.media.MediaRoute2Info;
 
+import com.android.media.flags.Flags;
+
 /* package */ final class AudioAttributesUtils {
 
     /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
@@ -36,6 +38,14 @@
     @MediaRoute2Info.Type
     /* package */ static int mapToMediaRouteType(
             @NonNull AudioDeviceAttributes audioDeviceAttributes) {
+        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            switch (audioDeviceAttributes.getType()) {
+                case AudioDeviceInfo.TYPE_HDMI_ARC:
+                    return MediaRoute2Info.TYPE_HDMI_ARC;
+                case AudioDeviceInfo.TYPE_HDMI_EARC:
+                    return MediaRoute2Info.TYPE_HDMI_EARC;
+            }
+        }
         switch (audioDeviceAttributes.getType()) {
             case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
             case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
@@ -48,6 +58,8 @@
             case AudioDeviceInfo.TYPE_DOCK_ANALOG:
                 return MediaRoute2Info.TYPE_DOCK;
             case AudioDeviceInfo.TYPE_HDMI:
+            case AudioDeviceInfo.TYPE_HDMI_ARC:
+            case AudioDeviceInfo.TYPE_HDMI_EARC:
                 return MediaRoute2Info.TYPE_HDMI;
             case AudioDeviceInfo.TYPE_USB_DEVICE:
                 return MediaRoute2Info.TYPE_USB_DEVICE;
@@ -62,7 +74,6 @@
         }
     }
 
-
     /* package */ static boolean isDeviceOutputAttributes(
             @Nullable AudioDeviceAttributes audioDeviceAttributes) {
         if (audioDeviceAttributes == null) {
@@ -81,6 +92,8 @@
             case AudioDeviceInfo.TYPE_DOCK:
             case AudioDeviceInfo.TYPE_DOCK_ANALOG:
             case AudioDeviceInfo.TYPE_HDMI:
+            case AudioDeviceInfo.TYPE_HDMI_ARC:
+            case AudioDeviceInfo.TYPE_HDMI_EARC:
             case AudioDeviceInfo.TYPE_USB_DEVICE:
                 return true;
             default:
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index eb997ba..8bc69c2 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -345,7 +345,7 @@
     }
 
     private void notifyBluetoothRoutesUpdated() {
-        mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+        mListener.onBluetoothRoutesUpdated();
     }
 
     private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 93f6ff3..360a6a7 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -22,6 +22,8 @@
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
@@ -160,7 +162,6 @@
     @NonNull
     private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
         int name = R.string.default_audio_route_name;
-
         switch (type) {
             case TYPE_WIRED_HEADPHONES:
             case TYPE_WIRED_HEADSET:
@@ -170,6 +171,8 @@
                 name = R.string.default_audio_route_name_dock_speakers;
                 break;
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 name = R.string.default_audio_route_name_external_device;
                 break;
             case TYPE_USB_DEVICE:
@@ -211,6 +214,8 @@
             case TYPE_WIRED_HEADSET:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
             case TYPE_USB_DEVICE:
                 return true;
             default:
@@ -231,7 +236,7 @@
             }
 
             if (isDeviceRouteChanged) {
-                mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+                mOnDeviceRouteChangedListener.onDeviceRouteChanged();
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index ddeeacc..2b01001 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -136,12 +136,8 @@
      */
     interface BluetoothRoutesUpdatedListener {
 
-        /**
-         * Called when Bluetooth routes have changed.
-         *
-         * @param routes updated Bluetooth routes list.
-         */
-        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+        /** Called when Bluetooth routes have changed. */
+        void onBluetoothRoutesUpdated();
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index e17f4a3..7876095 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -93,12 +93,8 @@
      */
     interface OnDeviceRouteChangedListener {
 
-        /**
-         * Called when device route has changed.
-         *
-         * @param deviceRoute non-null device route.
-         */
-        void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+        /** Called when device route has changed. */
+        void onDeviceRouteChanged();
     }
 
 }
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 1980403..ba3cecf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -280,7 +280,7 @@
 
     private void notifyBluetoothRoutesUpdated() {
         if (mListener != null) {
-            mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+            mListener.onBluetoothRoutesUpdated();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 971d11f..6ba40ae 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -165,8 +165,8 @@
         }
     }
 
-    private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
-        mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+    private void notifyDeviceRouteUpdate() {
+        mOnDeviceRouteChangedListener.onDeviceRouteChanged();
     }
 
     private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@@ -177,7 +177,7 @@
             synchronized (LegacyDeviceRouteController.this) {
                 mDeviceRoute = deviceRoute;
             }
-            notifyDeviceRouteUpdate(deviceRoute);
+            notifyDeviceRouteUpdate();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index cc261a4..44719f8 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -515,8 +515,7 @@
 
     // Binder call
     @Override
-    public RoutingSessionInfo getSystemSessionInfoForPackage(IMediaRouter2Manager manager,
-            String packageName) {
+    public RoutingSessionInfo getSystemSessionInfoForPackage(@Nullable String packageName) {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
         boolean setDeviceRouteSelected = false;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 95ca08c..a158b18 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -97,20 +97,23 @@
 public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
 
     /**
-     * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link
-     * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid
-     * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and
-     * above. For apps targeting Android T and below, the request will be ignored.
+     * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver(
+     * android.content.ComponentName)} throws an {@link
+     * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName}
+     * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver}
+     * for apps targeting Android U and above. For apps targeting Android T and below, the request
+     * will be ignored.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
 
     /**
-     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link
-     * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link
-     * android.app.Activity activity} for apps targeting Android V and above. For apps targeting
-     * Android U and below, the request will be ignored.
+     * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)}
+     * throws an {@link java.lang.IllegalArgumentException} if the provided
+     * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for
+     * apps targeting Android V and above. For apps targeting Android U and below, the request will
+     * be ignored.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 803ab28..2c59511 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.os.UserHandle.ALL;
 import static android.os.UserHandle.CURRENT;
+
 import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;
 import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;
 import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden;
@@ -267,7 +268,14 @@
             }
             if (record.isSystemPriority()) {
                 if (DEBUG_KEY_EVENT) {
-                    Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+                    Log.d(
+                            TAG,
+                            "Global priority session updated - user id="
+                                    + record.getUserId()
+                                    + " package="
+                                    + record.getPackageName()
+                                    + " active="
+                                    + record.isActive());
                 }
                 user.pushAddressedPlayerChangedLocked();
             } else {
@@ -1904,6 +1912,15 @@
                                 keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                         return;
                     }
+                    if (Flags.fallbackToDefaultHandlingWhenMediaSessionHasFixedVolumeHandling()
+                            && !record.canHandleVolumeKey()) {
+                        Log.d(TAG, "Session with packageName=" + record.getPackageName()
+                                + " doesn't support volume adjustment."
+                                + " Fallbacks to the default handling.");
+                        dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, true,
+                                keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
+                        return;
+                    }
                     switch (keyEvent.getAction()) {
                         case KeyEvent.ACTION_DOWN: {
                             int direction = 0;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6c9aa4b..78077a8 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -40,6 +40,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
 
 import java.util.List;
 import java.util.Objects;
@@ -109,21 +110,28 @@
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
-        mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> {
-            publishProviderState();
-            if (updateSessionInfosIfNeeded()) {
-                notifySessionInfoUpdated();
-            }
-        });
+        mBluetoothRouteController =
+                BluetoothRouteController.createInstance(
+                        context,
+                        () -> {
+                            publishProviderState();
+                            if (updateSessionInfosIfNeeded()) {
+                                notifySessionInfoUpdated();
+                            }
+                        });
 
-        mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
-            mHandler.post(() -> {
-                publishProviderState();
-                if (updateSessionInfosIfNeeded()) {
-                    notifySessionInfoUpdated();
-                }
-            });
-        });
+        mDeviceRouteController =
+                DeviceRouteController.createInstance(
+                        context,
+                        () -> {
+                            mHandler.post(
+                                    () -> {
+                                        publishProviderState();
+                                        if (updateSessionInfosIfNeeded()) {
+                                            notifySessionInfoUpdated();
+                                        }
+                                    });
+                        });
 
         mAudioManager.addOnDevicesForAttributesChangedListener(
                 AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
@@ -351,21 +359,8 @@
 
             RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
 
-            if (mPendingSessionCreationRequest != null) {
-                SessionCreationRequest sessionCreationRequest;
-                synchronized (mRequestLock) {
-                    sessionCreationRequest = mPendingSessionCreationRequest;
-                    mPendingSessionCreationRequest = null;
-                }
-                if (sessionCreationRequest != null) {
-                    if (TextUtils.equals(mSelectedRouteId, sessionCreationRequest.mRouteId)) {
-                        mCallback.onSessionCreated(this,
-                                sessionCreationRequest.mRequestId, newSessionInfo);
-                    } else {
-                        mCallback.onRequestFailed(this, sessionCreationRequest.mRequestId,
-                                MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
-                    }
-                }
+            synchronized (mRequestLock) {
+                reportPendingSessionRequestResultLockedIfNeeded(newSessionInfo);
             }
 
             if (Objects.equals(oldSessionInfo, newSessionInfo)) {
@@ -388,6 +383,59 @@
         }
     }
 
+    @GuardedBy("mRequestLock")
+    private void reportPendingSessionRequestResultLockedIfNeeded(
+            RoutingSessionInfo newSessionInfo) {
+        if (mPendingSessionCreationRequest == null) {
+            // No pending request, nothing to report.
+            return;
+        }
+
+        long pendingRequestId = mPendingSessionCreationRequest.mRequestId;
+        if (TextUtils.equals(mSelectedRouteId, mPendingSessionCreationRequest.mRouteId)) {
+            if (DEBUG) {
+                Slog.w(
+                        TAG,
+                        "Session creation success to route "
+                                + mPendingSessionCreationRequest.mRouteId);
+            }
+            mPendingSessionCreationRequest = null;
+            mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo);
+        } else {
+            boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute();
+            if (!Flags.enableWaitingStateForSystemSessionCreationRequest()
+                    || !isRequestedRouteConnectedBtRoute) {
+                if (DEBUG) {
+                    Slog.w(
+                            TAG,
+                            "Session creation failed to route "
+                                    + mPendingSessionCreationRequest.mRouteId);
+                }
+                mPendingSessionCreationRequest = null;
+                mCallback.onRequestFailed(
+                        this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
+            } else if (DEBUG) {
+                Slog.w(
+                        TAG,
+                        "Session creation waiting state to route "
+                                + mPendingSessionCreationRequest.mRouteId);
+            }
+        }
+    }
+
+    @GuardedBy("mRequestLock")
+    private boolean isRequestedRouteConnectedBtRoute() {
+        // Using AllRoutes instead of TransferableRoutes as BT Stack sends an intermediate update
+        // where two BT routes are active so the transferable routes list is empty.
+        // See b/307723189 for context
+        for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) {
+            if (TextUtils.equals(btRoute.getId(), mPendingSessionCreationRequest.mRouteId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void publishProviderState() {
         updateProviderState();
         notifyProviderState();
diff --git a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
new file mode 100644
index 0000000..5bad067
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Wrapper around {@link FrameworkStatsLog} */
+public class FrameworkStatsLogWrapper {
+
+    /** Wrapper around {@link FrameworkStatsLog#write}. */
+    public void write(
+            int code,
+            int sessionId,
+            int state,
+            int previousState,
+            int hostUid,
+            int targetUid,
+            int timeSinceLastActive,
+            int creationSource) {
+        FrameworkStatsLog.write(
+                code,
+                sessionId,
+                state,
+                previousState,
+                hostUid,
+                targetUid,
+                timeSinceLastActive,
+                creationSource);
+    }
+}
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 13d1662..893ed61 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -134,6 +134,7 @@
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     private MediaRouter.RouteInfo mMediaRouteInfo;
 
     @GuardedBy("mLock")
@@ -160,6 +161,7 @@
         mWmInternal = LocalServices.getService(WindowManagerInternal.class);
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
+        mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -193,6 +195,10 @@
         Looper createCallbackLooper() {
             return Looper.getMainLooper();
         }
+
+        MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+            return MediaProjectionMetricsLogger.getInstance(context);
+        }
     }
 
     @Override
@@ -286,6 +292,12 @@
     private void stopProjectionLocked(final MediaProjection projection) {
         Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
                 + "dispatching stop to callbacks");
+        ContentRecordingSession session = projection.mSession;
+        int targetUid =
+                session != null
+                        ? session.getTargetUid()
+                        : ContentRecordingSession.TARGET_UID_UNKNOWN;
+        mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid);
         mProjectionToken = null;
         mProjectionGrant = null;
         dispatchStop(projection);
@@ -372,6 +384,12 @@
             if (mProjectionGrant != null) {
                 // Cache the session details.
                 mProjectionGrant.mSession = incomingSession;
+                if (incomingSession != null) {
+                    // Only log in progress when session is not null.
+                    // setContentRecordingSession is called with a null session for the stop case.
+                    mMediaProjectionMetricsLogger.logInProgress(
+                            mProjectionGrant.uid, incomingSession.getTargetUid());
+                }
                 dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
             }
             return true;
@@ -441,6 +459,26 @@
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
     }
 
+    @VisibleForTesting
+    void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+        mMediaProjectionMetricsLogger.logInitiated(hostUid, sessionCreationSource);
+    }
+
+    @VisibleForTesting
+    void notifyPermissionRequestDisplayed(int hostUid) {
+        mMediaProjectionMetricsLogger.logPermissionRequestDisplayed(hostUid);
+    }
+
+    @VisibleForTesting
+    void notifyPermissionRequestCancelled(int hostUid) {
+        mMediaProjectionMetricsLogger.logProjectionPermissionRequestCancelled(hostUid);
+    }
+
+    @VisibleForTesting
+    void notifyAppSelectorDisplayed(int hostUid) {
+        mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid);
+    }
+
     /**
      * Handles result of dialog shown from
      * {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
@@ -818,6 +856,55 @@
         }
 
         @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+            notifyPermissionRequestInitiated_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
+                        hostUid, sessionCreationSource);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyPermissionRequestDisplayed(int hostUid) {
+            notifyPermissionRequestDisplayed_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyPermissionRequestCancelled(int hostUid) {
+            notifyPermissionRequestCancelled_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostUid);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyAppSelectorDisplayed(int hostUid) {
+            notifyAppSelectorDisplayed_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostUid);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
             final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
new file mode 100644
index 0000000..d7fefeb
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.time.Duration;
+
+/** Class for emitting logs describing a MediaProjection session. */
+public class MediaProjectionMetricsLogger {
+    private static final String TAG = "MediaProjectionMetricsLogger";
+
+    private static final int TARGET_UID_UNKNOWN = -2;
+    private static final int TIME_SINCE_LAST_ACTIVE_UNKNOWN = -1;
+
+    private static MediaProjectionMetricsLogger sSingleton = null;
+
+    private final FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
+    private final MediaProjectionSessionIdGenerator mSessionIdGenerator;
+    private final MediaProjectionTimestampStore mTimestampStore;
+
+    private int mPreviousState =
+            FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+
+    MediaProjectionMetricsLogger(
+            FrameworkStatsLogWrapper frameworkStatsLogWrapper,
+            MediaProjectionSessionIdGenerator sessionIdGenerator,
+            MediaProjectionTimestampStore timestampStore) {
+        mFrameworkStatsLogWrapper = frameworkStatsLogWrapper;
+        mSessionIdGenerator = sessionIdGenerator;
+        mTimestampStore = timestampStore;
+    }
+
+    /** Returns a singleton instance of {@link MediaProjectionMetricsLogger}. */
+    public static MediaProjectionMetricsLogger getInstance(Context context) {
+        if (sSingleton == null) {
+            sSingleton =
+                    new MediaProjectionMetricsLogger(
+                            new FrameworkStatsLogWrapper(),
+                            MediaProjectionSessionIdGenerator.getInstance(context),
+                            MediaProjectionTimestampStore.getInstance(context));
+        }
+        return sSingleton;
+    }
+
+    /**
+     * Logs that the media projection session was initiated by the app requesting the user's consent
+     * to capture. Should be sent even if the permission dialog is not shown.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     * @param sessionCreationSource Where this session started. One of:
+     *     <ul>
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP}
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST}
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER}
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN}
+     *     </ul>
+     */
+    public void logInitiated(int hostUid, int sessionCreationSource) {
+        Log.d(TAG, "logInitiated");
+        Duration durationSinceLastActiveSession = mTimestampStore.timeSinceLastActiveSession();
+        int timeSinceLastActiveInSeconds =
+                durationSinceLastActiveSession == null
+                        ? TIME_SINCE_LAST_ACTIVE_UNKNOWN
+                        : (int) durationSinceLastActiveSession.toSeconds();
+        write(
+                mSessionIdGenerator.createAndGetNewSessionId(),
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+                hostUid,
+                TARGET_UID_UNKNOWN,
+                timeSinceLastActiveInSeconds,
+                sessionCreationSource);
+    }
+
+    /**
+     * Logs that the user entered the setup flow and permission dialog is displayed. This state is
+     * not sent when the permission is already granted and we skipped showing the permission dialog.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     */
+    public void logPermissionRequestDisplayed(int hostUid) {
+        Log.d(TAG, "logPermissionRequestDisplayed");
+        write(
+                mSessionIdGenerator.getCurrentSessionId(),
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED,
+                hostUid,
+                TARGET_UID_UNKNOWN,
+                TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    /**
+     * Logs that requesting permission for media projection was cancelled by the user.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     */
+    public void logProjectionPermissionRequestCancelled(int hostUid) {
+        write(
+                mSessionIdGenerator.getCurrentSessionId(),
+                FrameworkStatsLog
+                        .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED,
+                hostUid,
+                TARGET_UID_UNKNOWN,
+                TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+                FrameworkStatsLog
+                        .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    /**
+     * Logs that the app selector dialog is shown for the user.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     */
+    public void logAppSelectorDisplayed(int hostUid) {
+        Log.d(TAG, "logAppSelectorDisplayed");
+        write(
+                mSessionIdGenerator.getCurrentSessionId(),
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED,
+                hostUid,
+                TARGET_UID_UNKNOWN,
+                TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    /**
+     * Logs that the virtual display is created and capturing the selected region begins.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     * @param targetUid UID of the package that is captured if selected.
+     */
+    public void logInProgress(int hostUid, int targetUid) {
+        Log.d(TAG, "logInProgress");
+        write(
+                mSessionIdGenerator.getCurrentSessionId(),
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+                hostUid,
+                targetUid,
+                TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    /**
+     * Logs that the capturing stopped, either normally or because of error.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     * @param targetUid UID of the package that is captured if selected.
+     */
+    public void logStopped(int hostUid, int targetUid) {
+        boolean wasCaptureInProgress =
+                mPreviousState
+                        == MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+        Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress);
+        write(
+                mSessionIdGenerator.getCurrentSessionId(),
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+                hostUid,
+                targetUid,
+                TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+
+        if (wasCaptureInProgress) {
+            mTimestampStore.registerActiveSessionEnded();
+        }
+    }
+
+    private void write(
+            int sessionId,
+            int state,
+            int hostUid,
+            int targetUid,
+            int timeSinceLastActive,
+            int creationSource) {
+        mFrameworkStatsLogWrapper.write(
+                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+                sessionId,
+                state,
+                mPreviousState,
+                hostUid,
+                targetUid,
+                timeSinceLastActive,
+                creationSource);
+        mPreviousState = state;
+    }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
new file mode 100644
index 0000000..244de0b
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+
+public class MediaProjectionSessionIdGenerator {
+
+    private static final String PREFERENCES_FILE_NAME = "media_projection_session_id";
+    private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key";
+    private static final int SESSION_ID_DEFAULT_VALUE = 0;
+
+    private static final Object sInstanceLock = new Object();
+
+    @GuardedBy("sInstanceLock")
+    private static MediaProjectionSessionIdGenerator sInstance;
+
+    private final Object mSessionIdLock = new Object();
+
+    @GuardedBy("mSessionIdLock")
+    private final SharedPreferences mSharedPreferences;
+
+    /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */
+    public static MediaProjectionSessionIdGenerator getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                File preferencesFile =
+                        new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+                // Needed as this class is instantiated before the device is unlocked.
+                Context directBootContext = context.createDeviceProtectedStorageContext();
+                SharedPreferences preferences =
+                        directBootContext.getSharedPreferences(
+                                preferencesFile, Context.MODE_PRIVATE);
+                sInstance = new MediaProjectionSessionIdGenerator(preferences);
+            }
+            return sInstance;
+        }
+    }
+
+    @VisibleForTesting
+    public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) {
+        this.mSharedPreferences = sharedPreferences;
+    }
+
+    /** Returns the current session ID. This value is persisted across reboots. */
+    public int getCurrentSessionId() {
+        synchronized (mSessionIdLock) {
+            return getCurrentSessionIdInternal();
+        }
+    }
+
+    /**
+     * Creates and returns a new session ID. This value will be persisted as the new current session
+     * ID, and will be persisted across reboots.
+     */
+    public int createAndGetNewSessionId() {
+        synchronized (mSessionIdLock) {
+            int newSessionId = getCurrentSessionId() + 1;
+            setSessionIdInternal(newSessionId);
+            return newSessionId;
+        }
+    }
+
+    @GuardedBy("mSessionIdLock")
+    private void setSessionIdInternal(int value) {
+        mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply();
+    }
+
+    @GuardedBy("mSessionIdLock")
+    private int getCurrentSessionIdInternal() {
+        return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE);
+    }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
new file mode 100644
index 0000000..bfec58c
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.InstantSource;
+
+/** Stores timestamps of media projection sessions. */
+public class MediaProjectionTimestampStore {
+    private static final String PREFERENCES_FILE_NAME = "media_projection_timestamp";
+    private static final String TIMESTAMP_PREF_KEY = "media_projection_timestamp_key";
+    private static final Object sInstanceLock = new Object();
+
+    @GuardedBy("sInstanceLock")
+    private static MediaProjectionTimestampStore sInstance;
+
+    private final Object mTimestampLock = new Object();
+
+    @GuardedBy("mTimestampLock")
+    private final SharedPreferences mSharedPreferences;
+
+    private final InstantSource mInstantSource;
+
+    @VisibleForTesting
+    public MediaProjectionTimestampStore(
+            SharedPreferences sharedPreferences, InstantSource instantSource) {
+        this.mSharedPreferences = sharedPreferences;
+        this.mInstantSource = instantSource;
+    }
+
+    /** Creates or returns an existing instance of {@link MediaProjectionTimestampStore}. */
+    public static MediaProjectionTimestampStore getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                File preferencesFile =
+                        new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+                // Needed as this class is instantiated before the device is unlocked.
+                Context directBootContext = context.createDeviceProtectedStorageContext();
+                SharedPreferences preferences =
+                        directBootContext.getSharedPreferences(
+                                preferencesFile, Context.MODE_PRIVATE);
+                sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system());
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Returns the time that has passed since the last active session, or {@code null} if there was
+     * no last active session.
+     */
+    @Nullable
+    public Duration timeSinceLastActiveSession() {
+        synchronized (mTimestampLock) {
+            Instant lastActiveSessionTimestamp = getLastActiveSessionTimestamp();
+            if (lastActiveSessionTimestamp == null) {
+                return null;
+            }
+            Instant now = mInstantSource.instant();
+            return Duration.between(lastActiveSessionTimestamp, now);
+        }
+    }
+
+    /** Registers that the current active session ended now. */
+    public void registerActiveSessionEnded() {
+        synchronized (mTimestampLock) {
+            Instant now = mInstantSource.instant();
+            setLastActiveSessionTimestamp(now);
+        }
+    }
+
+    @GuardedBy("mTimestampLock")
+    @Nullable
+    private Instant getLastActiveSessionTimestamp() {
+        long lastActiveSessionEpochMilli =
+                mSharedPreferences.getLong(TIMESTAMP_PREF_KEY, /* defValue= */ -1);
+        if (lastActiveSessionEpochMilli == -1) {
+            return null;
+        }
+        return Instant.ofEpochMilli(lastActiveSessionEpochMilli);
+    }
+
+    @GuardedBy("mTimestampLock")
+    private void setLastActiveSessionTimestamp(@NonNull Instant timestamp) {
+        mSharedPreferences.edit().putLong(TIMESTAMP_PREF_KEY, timestamp.toEpochMilli()).apply();
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 75a0cf5..6b7db2d 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -38,6 +38,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
@@ -48,6 +49,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.telephony.PhoneStateListener;
@@ -61,18 +63,21 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 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;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -87,15 +92,24 @@
     static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
             "debug.notification.interruptiveness", false);
 
+    private static final float DEFAULT_VOLUME = 1.0f;
+    // TODO (b/291899544): remove for release
+    private static final String POLITE_STRATEGY1 = "rule1";
+    private static final String POLITE_STRATEGY2 = "rule2";
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
+    private final UserManager mUm;
     private final NotificationManagerPrivate mNMP;
     private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
     private AccessibilityManager mAccessibilityManager;
     private KeyguardManager mKeyguardManager;
     private AudioManager mAudioManager;
-    private final LightsManager mLightsManager;
     private final NotificationUsageStats mUsageStats;
     private final ZenModeHelper mZenModeHelper;
 
@@ -126,17 +140,26 @@
     private final float mInCallNotificationVolume;
     private Binder mCallNotificationToken = null;
 
+    // Settings flags
+    private boolean mNotificationCooldownEnabled;
+    private boolean mNotificationCooldownForWorkEnabled;
+    private boolean mNotificationCooldownApplyToAll;
+    private boolean mNotificationCooldownVibrateUnlocked;
+
+    private boolean mEnablePoliteNotificationsFeature;
+    private final PolitenessStrategy mStrategy;
+    private int mCurrentWorkProfileId = UserHandle.USER_NULL;
 
     public NotificationAttentionHelper(Context context, LightsManager lightsManager,
             AccessibilityManager accessibilityManager, PackageManager packageManager,
-            NotificationUsageStats usageStats,
+            UserManager userManager, NotificationUsageStats usageStats,
             NotificationManagerPrivate notificationManagerPrivate,
             ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
         mContext = context;
         mPackageManager = packageManager;
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mAccessibilityManager = accessibilityManager;
-        mLightsManager = lightsManager;
+        mUm = userManager;
         mNMP = notificationManagerPrivate;
         mUsageStats = usageStats;
         mZenModeHelper = zenModeHelper;
@@ -144,8 +167,8 @@
 
         mVibratorHelper = new VibratorHelper(context);
 
-        mNotificationLight = mLightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
-        mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
+        mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
+        mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
         Resources resources = context.getResources();
         mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
@@ -169,7 +192,39 @@
                 .build();
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
+        mEnablePoliteNotificationsFeature = Flags.politeNotifications();
+
+        if (mEnablePoliteNotificationsFeature) {
+            mStrategy = getPolitenessStrategy();
+        } else {
+            mStrategy = null;
+        }
+
         mSettingsObserver = new SettingsObserver();
+        loadUserSettings();
+    }
+
+    private PolitenessStrategy getPolitenessStrategy() {
+        final String politenessStrategy = mFlagResolver.getStringValue(
+                NotificationFlags.NOTIF_COOLDOWN_RULE);
+
+        if (POLITE_STRATEGY2.equals(politenessStrategy)) {
+            return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2));
+        } else {
+            if (!POLITE_STRATEGY1.equals(politenessStrategy)) {
+                Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to "
+                        + POLITE_STRATEGY1);
+            }
+
+            return new Strategy1(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_COOLDOWN_COUNTER_RESET));
+        }
     }
 
     public void onSystemReady() {
@@ -202,11 +257,59 @@
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(Intent.ACTION_USER_PRESENT);
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
 
         mContext.getContentResolver().registerContentObserver(
                 SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver,
                 UserHandle.USER_ALL);
+        if (mEnablePoliteNotificationsFeature) {
+            mContext.getContentResolver().registerContentObserver(
+                    SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver,
+                    UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(
+                    SettingsObserver.NOTIFICATION_COOLDOWN_ALL_URI, false, mSettingsObserver,
+                    UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(
+                    SettingsObserver.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI, false,
+                    mSettingsObserver, UserHandle.USER_ALL);
+        }
+    }
+
+    private void loadUserSettings() {
+        if (mEnablePoliteNotificationsFeature) {
+            try {
+                mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
+
+                mNotificationCooldownEnabled =
+                    Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                        DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, UserHandle.USER_CURRENT) != 0;
+                if (mCurrentWorkProfileId != UserHandle.USER_NULL) {
+                    mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser(
+                        mContext.getContentResolver(),
+                        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                        DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, mCurrentWorkProfileId)
+                        != 0;
+                } else {
+                    mNotificationCooldownForWorkEnabled = false;
+                }
+                mNotificationCooldownApplyToAll = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL,
+                    UserHandle.USER_CURRENT) != 0;
+                mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                    DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                    UserHandle.USER_CURRENT) != 0;
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to read Settings: " + e);
+            }
+        }
     }
 
     @VisibleForTesting
@@ -229,6 +332,10 @@
             Log.d(TAG, "buzzBeepBlinkLocked " + record);
         }
 
+        if (isPoliteNotificationFeatureEnabled(record)) {
+            mStrategy.onNotificationPosted(record);
+        }
+
         // Should this notification make noise, vibe, or use the LED?
         final boolean aboveThreshold =
                 mIsAutomotive
@@ -269,6 +376,9 @@
                     vibration = mVibratorHelper.createFallbackVibration(insistent);
                 }
                 hasValidVibrate = vibration != null;
+                // Vibration-only if unlocked and Settings flag set
+                boolean vibrateOnly =
+                        hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
                     if (!sentAccessibilityEvent) {
@@ -277,7 +387,7 @@
                     }
                     if (DEBUG) Slog.v(TAG, "Interrupting!");
                     boolean isInsistentUpdate = isInsistentUpdate(record);
-                    if (hasValidSound) {
+                    if (hasValidSound && !vibrateOnly) {
                         if (isInsistentUpdate) {
                             // don't reset insistent sound, it's jarring
                             beep = true;
@@ -301,7 +411,7 @@
                         if (isInsistentUpdate) {
                             buzz = true;
                         } else {
-                            buzz = playVibration(record, vibration, hasValidSound);
+                            buzz = playVibration(record, vibration, hasValidSound && !vibrateOnly);
                             if (buzz) {
                                 mVibrateNotificationKey = key;
                             }
@@ -341,9 +451,7 @@
         } else if (wasShowLights) {
             updateLightsLocked();
         }
-        final int buzzBeepBlink =
-                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0);
-        if (buzzBeepBlink > 0) {
+        if (buzz || beep || blink) {
             // Ignore summary updates because we don't display most of the information.
             if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) {
                 if (DEBUG_INTERRUPTIVENESS) {
@@ -362,15 +470,43 @@
                             + record.getKey() + " is interruptive: alerted");
                 }
             }
+        }
+        final int buzzBeepBlinkLoggingCode =
+                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+        if (buzzBeepBlinkLoggingCode > 0) {
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                     .setType(MetricsEvent.TYPE_OPEN)
-                    .setSubtype(buzzBeepBlink));
-            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
+                    .setSubtype(buzzBeepBlinkLoggingCode));
+            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
+                    getPolitenessState(record));
         }
         record.setAudiblyAlerted(buzz || beep);
+        if (mEnablePoliteNotificationsFeature) {
+            // Update last alert time
+            if (buzz || beep) {
+                record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis());
+            }
+        }
+        return buzzBeepBlinkLoggingCode;
+    }
 
-        return buzzBeepBlink;
+    private int getPoliteBit(final NotificationRecord record) {
+        switch (getPolitenessState(record)) {
+            case PolitenessStrategy.POLITE_STATE_POLITE:
+                return MetricsProto.MetricsEvent.ALERT_POLITE;
+            case PolitenessStrategy.POLITE_STATE_MUTED:
+                return MetricsProto.MetricsEvent.ALERT_MUTED;
+            default:
+                return 0;
+        }
+    }
+
+    private int getPolitenessState(final NotificationRecord record) {
+        if (!isPoliteNotificationFeatureEnabled(record)) {
+            return PolitenessStrategy.POLITE_STATE_DEFAULT;
+        }
+        return mStrategy.getPolitenessState(record);
     }
 
     boolean isInsistentUpdate(final NotificationRecord record) {
@@ -468,7 +604,7 @@
                                 + record.getAudioAttributes());
                     }
                     player.playAsync(soundUri, record.getSbn().getUser(), looping,
-                            record.getAudioAttributes());
+                            record.getAudioAttributes(), getSoundVolume(record));
                     return true;
                 }
             } catch (RemoteException e) {
@@ -480,12 +616,56 @@
         return false;
     }
 
+    private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) {
+        // Check feature flag
+        if (!mEnablePoliteNotificationsFeature) {
+            return false;
+        }
+
+        // The user can enable/disable notifications cooldown from the Settings app
+        if (!mNotificationCooldownEnabled) {
+            return false;
+        }
+
+        // The user can enable/disable notifications cooldown for work profile from the Settings app
+        if (isNotificationForWorkProfile(record) && !mNotificationCooldownForWorkEnabled) {
+            return false;
+        }
+
+        // The user can choose to apply cooldown for all apps/conversations only from the
+        // Settings app
+        if (!mNotificationCooldownApplyToAll && record.getChannel().getConversationId() == null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private float getSoundVolume(final NotificationRecord record) {
+        if (!isPoliteNotificationFeatureEnabled(record)) {
+            return DEFAULT_VOLUME;
+        }
+
+        return mStrategy.getSoundVolume(record);
+    }
+
+    private float getVibrationIntensity(final NotificationRecord record) {
+        if (!isPoliteNotificationFeatureEnabled(record)) {
+            return DEFAULT_VOLUME;
+        }
+
+        return mStrategy.getVibrationIntensity(record);
+    }
+
     private boolean playVibration(final NotificationRecord record, final VibrationEffect effect,
             boolean delayVibForSound) {
         // Escalate privileges so we can use the vibrator even if the
         // notifying app does not have the VIBRATE permission.
         final long identity = Binder.clearCallingIdentity();
         try {
+            final float scale = getVibrationIntensity(record);
+            final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0
+                    ? mVibratorHelper.scale(effect, scale) : effect;
             if (delayVibForSound) {
                 new Thread(() -> {
                     // delay the vibration by the same amount as the notification sound
@@ -503,7 +683,7 @@
                     // so need to check that the notification is still valid for vibrate.
                     if (mNMP.getNotificationByKey(record.getKey()) != null) {
                         if (record.getKey().equals(mVibrateNotificationKey)) {
-                            vibrate(record, effect, true);
+                            vibrate(record, scaledEffect, true);
                         } else {
                             if (DEBUG) {
                                 Slog.v(TAG, "No vibration for notification "
@@ -517,7 +697,7 @@
                     }
                 }).start();
             } else {
-                vibrate(record, effect, false);
+                vibrate(record, scaledEffect, false);
             }
             return true;
         } finally {
@@ -535,7 +715,7 @@
     }
 
     void playInCallNotification() {
-        // TODO: Should we apply politeness to mInCallNotificationVolume ?
+        // TODO b/270456865: Should we apply politeness to mInCallNotificationVolume ?
         final ContentResolver cr = mContext.getContentResolver();
         if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL
                 && Settings.Secure.getIntForUser(cr,
@@ -760,6 +940,22 @@
                 || signals.isCurrentProfile);
     }
 
+    private boolean isNotificationForWorkProfile(final NotificationRecord record) {
+        return (record.getUser().getIdentifier() == mCurrentWorkProfileId
+                && mCurrentWorkProfileId != UserHandle.USER_NULL);
+    }
+
+    private int getManagedProfileId(int parentUserId) {
+        final List<UserInfo> profiles = mUm.getProfiles(parentUserId);
+        for (UserInfo profile : profiles) {
+            if (profile.isManagedProfile()
+                    && profile.getUserHandle().getIdentifier() != parentUserId) {
+                return profile.getUserHandle().getIdentifier();
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     void sendAccessibilityEvent(NotificationRecord record) {
         if (!mAccessibilityManager.isEnabled()) {
             return;
@@ -791,6 +987,16 @@
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
 
+    /**
+     * Notify the attention helper of a user interaction with a notification
+     * @param record that was interacted with
+     */
+    public void onUserInteraction(final NotificationRecord record) {
+        if (isPoliteNotificationFeatureEnabled(record)) {
+            mStrategy.onUserInteraction(record);
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
         pw.println("\n  Notification attention state:");
         pw.print(prefix);
@@ -834,6 +1040,243 @@
         }
     }
 
+    abstract private static class PolitenessStrategy {
+        static final int POLITE_STATE_DEFAULT = 0;
+        static final int POLITE_STATE_POLITE = 1;
+        static final int POLITE_STATE_MUTED = 2;
+
+        @IntDef(prefix = { "POLITE_STATE_" }, value = {
+                POLITE_STATE_DEFAULT,
+                POLITE_STATE_POLITE,
+                POLITE_STATE_MUTED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface PolitenessState {}
+
+        protected final Map<String, Integer> mVolumeStates;
+
+        // Cooldown timer for transitioning into polite state
+        protected final int mTimeoutPolite;
+        // Cooldown timer for transitioning into muted state
+        protected final int mTimeoutMuted;
+        // Volume for polite state
+        protected final float mVolumePolite;
+        // Volume for muted state
+        protected final float mVolumeMuted;
+
+        public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
+                int volumeMuted) {
+            mVolumeStates = new HashMap<>();
+
+            this.mTimeoutPolite = timeoutPolite;
+            this.mTimeoutMuted = timeoutMuted;
+            this.mVolumePolite = volumePolite / 100.0f;
+            this.mVolumeMuted = volumeMuted / 100.0f;
+        }
+
+        abstract void onNotificationPosted(NotificationRecord record);
+
+        String getChannelKey(final NotificationRecord record) {
+            // use conversationId if it's a conversation
+            String channelId = record.getChannel().getConversationId() != null
+                    ? record.getChannel().getConversationId() : record.getChannel().getId();
+            return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName()
+                    + ":" + channelId;
+        }
+
+        public float getSoundVolume(final NotificationRecord record) {
+            float volume = DEFAULT_VOLUME;
+            final String key = getChannelKey(record);
+            final @PolitenessState int volState = getPolitenessState(record);
+
+            switch (volState) {
+                case POLITE_STATE_DEFAULT:
+                    volume = DEFAULT_VOLUME;
+                    break;
+                case POLITE_STATE_POLITE:
+                    volume = mVolumePolite;
+                    break;
+                case POLITE_STATE_MUTED:
+                    volume = mVolumeMuted;
+                    break;
+                default:
+                    Log.w(TAG, "getSoundVolume unexpected volume state: " + volState);
+                    break;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG,
+                        "getSoundVolume state: " + volState + " vol: " + volume + " key: " + key);
+            }
+
+            return volume;
+        }
+
+        private float getVibrationIntensity(final NotificationRecord record) {
+            // TODO b/270456865: maybe use different scaling for vibration/sound ?
+            return getSoundVolume(record);
+        }
+
+        public void onUserInteraction(final NotificationRecord record) {
+            final String key = getChannelKey(record);
+            // reset to default state after user interaction
+            mVolumeStates.put(key, POLITE_STATE_DEFAULT);
+            record.getChannel().setLastNotificationUpdateTimeMs(0);
+        }
+
+        public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
+            return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
+        }
+    }
+
+    // TODO b/270456865: Only one of the two strategies will be released.
+    //  The other one need to be removed
+    /**
+     *  Polite notification strategy 1:
+     *   - Transitions from default (loud) => polite (lower volume) state if a notification
+     *  alerts the same channel before timeoutPolite.
+     *   - Transitions from polite => muted state if a notification alerts the same channel
+     *   before timeoutMuted OR transitions back to the default state if a notification alerts
+     *   after timeoutPolite.
+     *   - Transitions from muted => default state if the muted channel received more than maxPosted
+     *  notifications OR transitions back to the polite state if a notification alerts
+     *  after timeoutMuted.
+     *  - Transitions back to the default state after a user interaction with a notification.
+     */
+    public static class Strategy1 extends PolitenessStrategy {
+        // Keep track of the number of notifications posted per channel
+        private final Map<String, Integer> mNumPosted;
+        // Reset to default state if number of posted notifications exceed this value when muted
+        private final int mMaxPostedForReset;
+
+        public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted,
+                int maxPosted) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+
+            mNumPosted = new HashMap<>();
+            mMaxPostedForReset = maxPosted;
+
+            if (DEBUG) {
+                Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted);
+            }
+        }
+
+        @Override
+        public void onNotificationPosted(final NotificationRecord record) {
+            long timeSinceLastNotif = System.currentTimeMillis()
+                    - record.getChannel().getLastNotificationUpdateTimeMs();
+
+            final String key = getChannelKey(record);
+            @PolitenessState int volState = getPolitenessState(record);
+
+            int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
+            mNumPosted.put(key, numPosted);
+
+            switch (volState) {
+                case POLITE_STATE_DEFAULT:
+                    if (timeSinceLastNotif < mTimeoutPolite) {
+                        volState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                case POLITE_STATE_POLITE:
+                    if (timeSinceLastNotif < mTimeoutMuted) {
+                        volState = POLITE_STATE_MUTED;
+                    } else if (timeSinceLastNotif > mTimeoutPolite) {
+                        volState = POLITE_STATE_DEFAULT;
+                    } else {
+                        volState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                case POLITE_STATE_MUTED:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        volState = POLITE_STATE_POLITE;
+                    } else {
+                        volState = POLITE_STATE_MUTED;
+                    }
+                    if (numPosted >= mMaxPostedForReset) {
+                        volState = POLITE_STATE_DEFAULT;
+                        mNumPosted.put(key, 0);
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
+                    break;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
+                        + volState + " key: " + key + " numposted " + numPosted);
+            }
+
+            mVolumeStates.put(key, volState);
+        }
+
+        @Override
+        public void onUserInteraction(final NotificationRecord record) {
+            super.onUserInteraction(record);
+            mNumPosted.put(getChannelKey(record), 0);
+        }
+    }
+
+    /**
+     *  Polite notification strategy 2:
+     *   - Transitions from default (loud) => muted state if a notification
+     *   alerts the same channel before timeoutPolite.
+     *   - Transitions from polite => default state if a notification
+     *  alerts the same channel before timeoutMuted.
+     *   - Transitions from muted => default state if a notification alerts after timeoutMuted,
+     *  otherwise transitions to the polite state.
+     *   - Transitions back to the default state after a user interaction with a notification.
+     */
+    public static class Strategy2 extends PolitenessStrategy {
+        public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+
+            if (DEBUG) {
+                Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted);
+            }
+        }
+
+        @Override
+        public void onNotificationPosted(final NotificationRecord record) {
+            long timeSinceLastNotif = System.currentTimeMillis()
+                    - record.getChannel().getLastNotificationUpdateTimeMs();
+
+            final String key = getChannelKey(record);
+            @PolitenessState int volState = getPolitenessState(record);
+
+            switch (volState) {
+                case POLITE_STATE_DEFAULT:
+                    if (timeSinceLastNotif < mTimeoutPolite) {
+                        volState = POLITE_STATE_MUTED;
+                    }
+                    break;
+                case POLITE_STATE_POLITE:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        volState = POLITE_STATE_DEFAULT;
+                    }
+                    break;
+                case POLITE_STATE_MUTED:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        volState = POLITE_STATE_DEFAULT;
+                    } else {
+                        volState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
+                    break;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
+                        + volState + " key: " + key);
+            }
+
+            mVolumeStates.put(key, volState);
+        }
+    }
+
     //======================  Observers  =============================
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -859,6 +1302,11 @@
                 if (mNotificationLight != null) {
                     mNotificationLight.turnOff();
                 }
+            } else if (action.equals(Intent.ACTION_USER_ADDED)
+                        || action.equals(Intent.ACTION_USER_REMOVED)
+                        || action.equals(Intent.ACTION_USER_SWITCHED)
+                        || action.equals(Intent.ACTION_USER_UNLOCKED)) {
+                loadUserSettings();
             }
         }
     };
@@ -867,6 +1315,12 @@
 
         private static final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(
                 Settings.System.NOTIFICATION_LIGHT_PULSE);
+        private static final Uri NOTIFICATION_COOLDOWN_ENABLED_URI = Settings.System.getUriFor(
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED);
+        private static final Uri NOTIFICATION_COOLDOWN_ALL_URI = Settings.System.getUriFor(
+                Settings.System.NOTIFICATION_COOLDOWN_ALL);
+        private static final Uri NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI =
+                Settings.System.getUriFor(Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED);
         public SettingsObserver() {
             super(null);
         }
@@ -884,11 +1338,45 @@
                     updateLightsLocked();
                 }
             }
+            if (mEnablePoliteNotificationsFeature) {
+                if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) {
+                    mNotificationCooldownEnabled = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                            DEFAULT_NOTIFICATION_COOLDOWN_ENABLED,
+                            UserHandle.USER_CURRENT) != 0;
+
+                    if (mCurrentWorkProfileId != UserHandle.USER_NULL) {
+                        mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser(
+                                mContext.getContentResolver(),
+                                Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                                DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK,
+                                mCurrentWorkProfileId)
+                                != 0;
+                    } else {
+                        mNotificationCooldownForWorkEnabled = false;
+                    }
+                }
+                if (NOTIFICATION_COOLDOWN_ALL_URI.equals(uri)) {
+                    mNotificationCooldownApplyToAll = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.NOTIFICATION_COOLDOWN_ALL,
+                            DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT)
+                            != 0;
+                }
+                if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
+                    mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                            DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                            UserHandle.USER_CURRENT) != 0;
+                }
+            }
         }
     }
 
 
-    //TODO: cleanup most (all?) of these
+    // TODO b/270456865: cleanup most (all?) of these
     //======================= FOR TESTS =====================
     @VisibleForTesting
     void setIsAutomotive(boolean isAutomotive) {
@@ -931,6 +1419,11 @@
     }
 
     @VisibleForTesting
+    void setUserPresent(boolean userPresent) {
+        mUserPresent = userPresent;
+    }
+
+    @VisibleForTesting
     void setLights(LogicalLight light) {
         mNotificationLight = light;
         mAttentionLight = light;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 87c3067..4b5d52f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -71,6 +71,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.os.Flags.allowPrivateProfile;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
@@ -243,6 +244,7 @@
 import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
 import android.service.notification.ConversationChannelWrapper;
@@ -289,7 +291,6 @@
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.MetricsLogger;
@@ -1179,7 +1180,7 @@
         @Override
         public void onSetDisabled(int status) {
             synchronized (mNotificationLock) {
-                if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.updateDisableNotificationEffectsLocked(status);
                 } else {
                     mDisableNotificationEffects =
@@ -1325,7 +1326,7 @@
         public void clearEffects() {
             synchronized (mNotificationLock) {
                 if (DBG) Slog.d(TAG, "clearEffects");
-                if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.clearAttentionEffects();
                 } else {
                     clearSoundLocked();
@@ -1554,8 +1555,7 @@
                         int changedFlags = data.getFlags() ^ flags;
                         if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
                             // Suppress notification flag changed, clear any effects
-                            if (mFlagResolver.isEnabled(
-                                    NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                            if (Flags.refactorAttentionHelper()) {
                                 mAttentionHelper.clearEffectsLocked(key);
                             } else {
                                 clearEffectsLocked(key);
@@ -1904,7 +1904,7 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
 
-            if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (!Flags.refactorAttentionHelper()) {
                 if (action.equals(Intent.ACTION_SCREEN_ON)) {
                     // Keep track of screen on/off state, but do not turn off the notification light
                     // until user passes through the lock screen or views the notification.
@@ -1931,7 +1931,8 @@
                     cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
                             REASON_USER_STOPPED);
                 }
-            } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
+            } else if (
+                    isProfileUnavailable(action)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
                     cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
@@ -1982,6 +1983,12 @@
                 }
             }
         }
+
+        private boolean isProfileUnavailable(String action) {
+            return allowPrivateProfile() ?
+                    action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
+                    action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        }
     };
 
     private final class SettingsObserver extends ContentObserver {
@@ -2002,6 +2009,8 @@
                         Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS
                 = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        private final Uri SHOW_NOTIFICATION_SNOOZE
+                = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE);
 
         SettingsObserver(Handler handler) {
             super(handler);
@@ -2011,7 +2020,7 @@
             ContentResolver resolver = getContext().getContentResolver();
             resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
                     false, this, UserHandle.USER_ALL);
-            if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (!Flags.refactorAttentionHelper()) {
                 resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
                     false, this, UserHandle.USER_ALL);
             }
@@ -2028,6 +2037,10 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS,
                     false, this, UserHandle.USER_ALL);
+
+            resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE,
+                    false, this, UserHandle.USER_ALL);
+
             update(null);
         }
 
@@ -2037,7 +2050,7 @@
 
         public void update(Uri uri) {
             ContentResolver resolver = getContext().getContentResolver();
-            if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (!Flags.refactorAttentionHelper()) {
                 if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
                     boolean pulseEnabled = Settings.System.getIntForUser(resolver,
                         Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2077,6 +2090,14 @@
             if (uri == null || LOCK_SCREEN_SHOW_NOTIFICATIONS.equals(uri)) {
                 mPreferencesHelper.updateLockScreenShowNotifications();
             }
+            if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) {
+                final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver,
+                        Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT)
+                        != 0;
+                if (!snoozeEnabled) {
+                    unsnoozeAll();
+                }
+            }
         }
     }
 
@@ -2530,9 +2551,9 @@
 
         mToastRateLimiter = toastRateLimiter;
 
-        if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+        if (Flags.refactorAttentionHelper()) {
             mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
-                mAccessibilityManager, mPackageManagerClient, usageStats,
+                mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
                 mNotificationManagerPrivate, mZenModeHelper, flagResolver);
         }
 
@@ -2540,7 +2561,7 @@
         // If this is called within a test, make sure to unregister the intent receivers by
         // calling onDestroy()
         IntentFilter filter = new IntentFilter();
-        if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+        if (!Flags.refactorAttentionHelper()) {
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2552,6 +2573,9 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        if (allowPrivateProfile()){
+            filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
+        }
         getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
 
         IntentFilter pkgFilter = new IntentFilter();
@@ -2865,7 +2889,7 @@
             }
             registerNotificationPreferencesPullers();
             new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.onSystemReady();
             }
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -3369,6 +3393,10 @@
         mAppUsageStats.reportEvent(r.getSbn().getPackageName(),
                 getRealUserId(r.getSbn().getUserId()),
                 UsageEvents.Event.USER_INTERACTION);
+
+        if (Flags.politeNotifications()) {
+            mAttentionHelper.onUserInteraction(r);
+        }
     }
 
     private int getRealUserId(int userId) {
@@ -3496,8 +3524,19 @@
                                 null /* options */);
                         record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,
                                 text, callback, duration, windowToken, displayId, textCallback);
-                        mToastQueue.add(record);
-                        index = mToastQueue.size() - 1;
+
+                        // Insert system toasts at the front of the queue
+                        int systemToastInsertIdx = mToastQueue.size();
+                        if (isSystemToast) {
+                            systemToastInsertIdx = getInsertIndexForSystemToastLocked();
+                        }
+                        if (systemToastInsertIdx < mToastQueue.size()) {
+                            index = systemToastInsertIdx;
+                            mToastQueue.add(index, record);
+                        } else {
+                            mToastQueue.add(record);
+                            index = mToastQueue.size() - 1;
+                        }
                         keepProcessAliveForToastIfNeededLocked(callingPid);
                     }
                     // If it's at index 0, it's the current toast.  It doesn't matter if it's
@@ -3513,6 +3552,23 @@
             }
         }
 
+        @GuardedBy("mToastQueue")
+        private int getInsertIndexForSystemToastLocked() {
+            // If there are other system toasts: insert after the last one
+            int idx = 0;
+            for (ToastRecord r : mToastQueue) {
+                if (idx == 0 && mIsCurrentToastShown) {
+                    idx++;
+                    continue;
+                }
+                if (!r.isSystemToast) {
+                    return idx;
+                }
+                idx++;
+            }
+            return idx;
+        }
+
         private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId,
                 boolean isAppRenderedToast, boolean isSystemToast) {
             final boolean isPackageSuspended = isPackagePaused(pkg);
@@ -5730,13 +5786,18 @@
         public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
                 boolean granted, boolean userSet) {
             Objects.requireNonNull(listener);
+            if (UserHandle.getCallingUserId() != userId) {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        "setNotificationListenerAccessGrantedForUser for user " + userId);
+            }
             checkNotificationListenerAccess();
             if (granted && listener.flattenToString().length()
                     > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
                 throw new IllegalArgumentException(
                         "Component name too long: " + listener.flattenToString());
             }
-            if (!userSet && isNotificationListenerAccessUserSet(listener)) {
+            if (!userSet && isNotificationListenerAccessUserSet(listener, userId)) {
                 // Don't override user's choice
                 return;
             }
@@ -5762,9 +5823,8 @@
             }
         }
 
-        private boolean isNotificationListenerAccessUserSet(ComponentName listener) {
-            return mListeners.isPackageOrComponentUserSet(listener.flattenToString(),
-                    getCallingUserHandle().getIdentifier());
+        private boolean isNotificationListenerAccessUserSet(ComponentName listener, int userId) {
+            return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), userId);
         }
 
         @Override
@@ -6454,7 +6514,7 @@
                     pw.println("  mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
                     pw.println("  hideSilentStatusBar="
                             + mPreferencesHelper.shouldHideSilentStatusIcons());
-                    if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.dump(pw, "    ", filter);
                     }
                 }
@@ -7720,7 +7780,7 @@
             boolean wasPosted = removeFromNotificationListsLocked(r);
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
                     SystemClock.elapsedRealtime());
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
@@ -7747,6 +7807,13 @@
         }
     }
 
+    private void unsnoozeAll() {
+        synchronized (mNotificationLock) {
+            mSnoozeHelper.repostAll(mUserProfiles.getCurrentProfileIds());
+            handleSavePolicyFile();
+        }
+    }
+
     protected class CancelNotificationRunnable implements Runnable {
         private final int mCallingUid;
         private final int mCallingPid;
@@ -7853,7 +7920,7 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker, mReason,
                             mCancellationElapsedTimeMs);
-                    if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.updateLightsLocked();
                     } else {
                         updateLightsLocked();
@@ -8150,7 +8217,7 @@
 
                     int buzzBeepBlinkLoggingCode = 0;
                     if (!r.isHidden()) {
-                        if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                        if (Flags.refactorAttentionHelper()) {
                             buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
                                 new NotificationAttentionHelper.Signals(
                                     mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -8612,7 +8679,7 @@
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                     .setType(MetricsEvent.TYPE_OPEN)
                     .setSubtype(buzzBeepBlink));
-            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
+            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, 0);
         }
         record.setAudiblyAlerted(buzz || beep);
         return buzzBeepBlink;
@@ -8757,7 +8824,7 @@
                     if (DBG) Slog.v(TAG, "Playing sound " + soundUri
                             + " with attributes " + record.getAudioAttributes());
                     player.playAsync(soundUri, record.getSbn().getUser(), looping,
-                            record.getAudioAttributes());
+                            record.getAudioAttributes(), 1.0f);
                     return true;
                 }
             } catch (RemoteException e) {
@@ -9137,7 +9204,7 @@
                     || interruptiveChanged;
             if (interceptBefore && !record.isIntercepted()
                     && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
-                if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.buzzBeepBlinkLocked(record,
                         new NotificationAttentionHelper.Signals(
                             mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9517,7 +9584,7 @@
                 });
             }
 
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.clearEffectsLocked(canceledKey);
             } else {
                 // sound
@@ -9881,7 +9948,7 @@
                             cancellationElapsedTimeMs);
                 }
             }
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
index 72c4529..9f16662 100644
--- a/services/core/java/com/android/server/notification/OWNERS
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -1,6 +1,9 @@
-# Bug component: 1305560
+# Bug component: 78010
 
 juliacr@google.com
 yurilin@google.com
+aroederer@google.com
+matiashe@google.com
+valiiftime@google.com
 jeffdq@google.com
 dsandler@android.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 0176989..8f5676b3 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -296,6 +296,20 @@
         }
     }
 
+    /**
+     * Unsnooze & repost all snoozed notifications for userId and its profiles
+     */
+    protected void repostAll(IntArray userIds) {
+        synchronized (mLock) {
+            List<NotificationRecord> snoozedNotifications = getSnoozed();
+            for (NotificationRecord r : snoozedNotifications) {
+                if (userIds.binarySearch(r.getUserId()) >= 0) {
+                    repost(r.getKey(), r.getUserId(), false);
+                }
+            }
+        }
+    }
+
     protected void repost(String key, boolean muteOnReturn) {
         synchronized (mLock) {
             final NotificationRecord r = mSnoozedNotifications.get(key);
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index e5d07bc..7204d05 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -50,6 +50,7 @@
     private final long[] mFallbackPattern;
     @Nullable private final float[] mDefaultPwlePattern;
     @Nullable private final float[] mFallbackPwlePattern;
+    private final int mDefaultVibrationAmplitude;
 
     public VibratorHelper(Context context) {
         mVibrator = context.getSystemService(Vibrator.class);
@@ -65,6 +66,8 @@
                 com.android.internal.R.array.config_defaultNotificationVibeWaveform);
         mFallbackPwlePattern = getFloatArray(context.getResources(),
                 com.android.internal.R.array.config_notificationFallbackVibeWaveform);
+        mDefaultVibrationAmplitude = context.getResources().getInteger(
+            com.android.internal.R.integer.config_defaultVibrationAmplitude);
     }
 
     /**
@@ -136,6 +139,14 @@
     }
 
     /**
+     *  Scale vibration effect, valid range is [0.0f, 1.0f]
+     *  Resolves default amplitude value if not already set.
+     */
+    public VibrationEffect scale(VibrationEffect effect, float scale) {
+        return effect.resolve(mDefaultVibrationAmplitude).scale(scale);
+    }
+
+    /**
      * Vibrate the device with given {@code effect}.
      *
      * <p>We need to vibrate as "android" so we can breakthrough DND.
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 4c6110b..d002688 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -35,8 +35,8 @@
 import android.util.Slog;
 
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
 import com.android.server.pm.UserRestrictionsUtils;
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1134714..e57ea0f 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.os;
 
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -52,8 +54,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.OptionalInt;
+import java.util.Set;
 
 /**
  * Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -101,10 +105,12 @@
         private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
                 new ArrayMap<>();
 
+        @GuardedBy("mLock")
+        private final Set<String> mBugreportFilesToPersist = new HashSet<>();
+
         /**
          * Checks that a given file was generated on behalf of the given caller. If the file was
-         * generated on behalf of the caller, it is removed from the bugreport mapping so that it
-         * may not be retrieved again. If the file was not generated on behalf of the caller, an
+         * not generated on behalf of the caller, an
          * {@link IllegalArgumentException} is thrown.
          *
          * @param callingInfo a (uid, package name) pair identifying the caller
@@ -114,35 +120,76 @@
          * @throws IllegalArgumentException if {@code bugreportFile} is not associated with
          *                                  {@code callingInfo}.
          */
+        @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
+                conditional = true)
         void ensureCallerPreviouslyGeneratedFile(
-                Pair<Integer, String> callingInfo, String bugreportFile) {
+                Context context, Pair<Integer, String> callingInfo, int userId,
+                String bugreportFile) {
             synchronized (mLock) {
-                ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
-                if (bugreportFilesForCaller != null
-                        && bugreportFilesForCaller.contains(bugreportFile)) {
-                    bugreportFilesForCaller.remove(bugreportFile);
-                    if (bugreportFilesForCaller.isEmpty()) {
-                        mBugreportFiles.remove(callingInfo);
+                if (onboardingBugreportV2Enabled()) {
+                    final int uidForUser = Binder.withCleanCallingIdentity(() -> {
+                        try {
+                            return context.getPackageManager()
+                                    .getPackageUidAsUser(callingInfo.second, userId);
+                        } catch (PackageManager.NameNotFoundException exception) {
+                            throwInvalidBugreportFileForCallerException(
+                                    bugreportFile, callingInfo.second);
+                            return -1;
+                        }
+                    });
+                    if (uidForUser != callingInfo.first && context.checkCallingOrSelfPermission(
+                            Manifest.permission.INTERACT_ACROSS_USERS)
+                            != PackageManager.PERMISSION_GRANTED) {
+                        throw new SecurityException(
+                                callingInfo.second + " does not hold the "
+                                        + "INTERACT_ACROSS_USERS permission to access "
+                                        + "cross-user bugreports.");
+                    }
+                    ArraySet<String> bugreportFilesForUid = mBugreportFiles.get(
+                            new Pair<>(uidForUser, callingInfo.second));
+                    if (bugreportFilesForUid == null
+                            || !bugreportFilesForUid.contains(bugreportFile)) {
+                        throwInvalidBugreportFileForCallerException(
+                                bugreportFile, callingInfo.second);
                     }
                 } else {
-                    throw new IllegalArgumentException(
-                            "File " + bugreportFile + " was not generated"
-                                    + " on behalf of calling package " + callingInfo.second);
+                    ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
+                    if (bugreportFilesForCaller != null
+                            && bugreportFilesForCaller.contains(bugreportFile)) {
+                        bugreportFilesForCaller.remove(bugreportFile);
+                        if (bugreportFilesForCaller.isEmpty()) {
+                            mBugreportFiles.remove(callingInfo);
+                        }
+                    } else {
+                        throwInvalidBugreportFileForCallerException(
+                                bugreportFile, callingInfo.second);
+
+                    }
                 }
             }
         }
 
+        private static void throwInvalidBugreportFileForCallerException(
+                String bugreportFile, String packageName) {
+            throw new IllegalArgumentException("File " + bugreportFile + " was not generated on"
+                    + " behalf of calling package " + packageName);
+        }
+
         /**
          * Associates a bugreport file with a caller, which is identified as a
          * (uid, package name) pair.
          */
-        void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
+        void addBugreportFileForCaller(
+                Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval) {
             synchronized (mLock) {
                 if (!mBugreportFiles.containsKey(caller)) {
                     mBugreportFiles.put(caller, new ArraySet<>());
                 }
                 ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
                 bugreportFilesForCaller.add(bugreportFile);
+                if ((onboardingBugreportV2Enabled()) && keepOnRetrieval) {
+                    mBugreportFilesToPersist.add(bugreportFile);
+                }
             }
         }
     }
@@ -246,16 +293,17 @@
     }
 
     @Override
-    @RequiresPermission(Manifest.permission.DUMP)
-    public void retrieveBugreport(int callingUidUnused, String callingPackage,
-            FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
+    @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
+    public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
+            FileDescriptor bugreportFd, String bugreportFile,
+            boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, false);
 
         Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
         try {
             mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                    new Pair<>(callingUid, callingPackage), bugreportFile);
+                    mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, e.getMessage());
             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
@@ -281,10 +329,17 @@
             // Wrap the listener so we can intercept binder events directly.
             DumpstateListener myListener = new DumpstateListener(listener, ds,
                     new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+
+            boolean keepBugreportOnRetrieval = false;
+            if (onboardingBugreportV2Enabled()) {
+                keepBugreportOnRetrieval = mBugreportFileManager.mBugreportFilesToPersist.contains(
+                        bugreportFile);
+            }
+
             setCurrentDumpstateListenerLocked(myListener);
             try {
-                ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
-                        bugreportFile, myListener);
+                ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
+                        bugreportFile, keepBugreportOnRetrieval, myListener);
             } catch (RemoteException e) {
                 Slog.e(TAG, "RemoteException in retrieveBugreport", e);
             }
@@ -317,7 +372,8 @@
     private void validateBugreportFlags(int flags) {
         flags = clearBugreportFlag(flags,
                 BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
-                        | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
+                        | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT
+                        | BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL);
         if (flags != 0) {
             Slog.w(TAG, "Unknown bugreport flags: " + flags);
             throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -482,6 +538,9 @@
         boolean reportFinishedFile =
                 (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
 
+        boolean keepBugreportOnRetrieval =
+                (bugreportFlags & BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL) != 0;
+
         IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
         if (ds == null) {
             Slog.w(TAG, "Unable to get bugreport service");
@@ -490,7 +549,8 @@
         }
 
         DumpstateListener myListener = new DumpstateListener(listener, ds,
-                new Pair<>(callingUid, callingPackage), reportFinishedFile);
+                new Pair<>(callingUid, callingPackage), reportFinishedFile,
+                keepBugreportOnRetrieval);
         setCurrentDumpstateListenerLocked(myListener);
         try {
             ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
@@ -646,9 +706,16 @@
         private final boolean mReportFinishedFile;
         private int mProgress; // used for debugging purposes only
         private boolean mDone;
+        private boolean mKeepBugreportOnRetrieval;
 
         DumpstateListener(IDumpstateListener listener, IDumpstate ds,
                 Pair<Integer, String> caller, boolean reportFinishedFile) {
+            this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false);
+        }
+
+        DumpstateListener(IDumpstateListener listener, IDumpstate ds,
+                Pair<Integer, String> caller, boolean reportFinishedFile,
+                boolean keepBugreportOnRetrieval) {
             if (DEBUG) {
                 Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
             }
@@ -656,6 +723,7 @@
             mDs = ds;
             mCaller = caller;
             mReportFinishedFile = reportFinishedFile;
+            mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
             try {
                 mDs.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -690,7 +758,8 @@
                 reportFinishedLocked("File: " + bugreportFile);
             }
             if (mReportFinishedFile) {
-                mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+                mBugreportFileManager.addBugreportFileForCaller(
+                        mCaller, bugreportFile, mKeepBugreportOnRetrieval);
             } else if (DEBUG) {
                 Slog.d(TAG, "Not reporting finished file");
             }
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index c7c8136..79c1346 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -569,6 +569,10 @@
 
         @Override
         public void onEvent(int event, @Nullable String path) {
+            if (path == null) {
+                Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()");
+                return;
+            }
             mHandler.post(() -> {
                 // Ignore .tmp files.
                 if (path.endsWith(".tmp")) {
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index e53c436..ca149c5 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -219,6 +219,7 @@
         case Process.AUDIOSERVER_UID:  // fastcapture, fastmixer
         case Process.CAMERASERVER_UID: // camera high frame rate recording
         case Process.BLUETOOTH_UID:    // Bluetooth audio playback
+        case Process.PHONE_UID:        // phone call
             return true;
         default:
             return false;
diff --git a/services/core/java/com/android/server/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS
new file mode 100644
index 0000000..6f322ee
--- /dev/null
+++ b/services/core/java/com/android/server/pdb/OWNERS
@@ -0,0 +1,2 @@
+victorhsieh@google.com
+swillden@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
similarity index 98%
rename from services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
rename to services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
index 21fa9f9..66ad716 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.pdb;
 
 /**
  * Internal interface for storing and retrieving persistent data.
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
similarity index 99%
rename from services/core/java/com/android/server/PersistentDataBlockService.java
rename to services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 754a7ed..b006ac8 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.pdb;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 
@@ -37,6 +37,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index bd9be30..167c17c 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -121,7 +121,7 @@
                 StorageManagerInternal.class);
         for (UserInfo user : umInternal.getUsers(false /*excludeDying*/)) {
             final int flags;
-            if (StorageManager.isUserKeyUnlocked(user.id)
+            if (StorageManager.isCeStorageUnlocked(user.id)
                     && smInternal.isCeStoragePrepared(user.id)) {
                 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
             } else if (umInternal.isUserRunning(user.id)) {
@@ -410,7 +410,7 @@
         // First look for stale data that doesn't belong, and check if things
         // have changed since we did our last restorecon
         if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
-            if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) {
+            if (StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId)) {
                 throw new RuntimeException(
                         "Yikes, someone asked us to reconcile CE storage while " + userId
                                 + " was still locked; this would have caused massive data loss!");
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index f987629..79cd2a0 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -489,6 +489,9 @@
 
     boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
 
+    /** Check if the package is in a stopped state for a given user. */
+    boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId);
+
     boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5d2944e..3ed9f02 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,7 +24,6 @@
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
 import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -505,6 +504,10 @@
             int filterCallingUid, int userId, boolean resolveForStart,
             boolean allowDynamicSplits) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
+
+        // Allow to match activities of quarantined packages.
+        flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
         final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
         enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */,
@@ -647,11 +650,6 @@
         flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
                 false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
 
-        // Only if the query is coming from the system process,
-        // it should be allowed to match quarantined components
-        if (callingUid != Process.SYSTEM_UID) {
-            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
-        }
         Intent originalIntent = null;
         ComponentName comp = intent.getComponent();
         if (comp == null) {
@@ -1526,9 +1524,6 @@
             ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
             ai.volumeUuid = ps.getVolumeUuid();
             ai.storageUuid = StorageManager.convert(ai.volumeUuid);
-            if (ps.isDefaultToDeviceProtectedStorage()) {
-                ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
-            }
             ai.setVersionCode(ps.getVersionCode());
             ai.flags = ps.getFlags();
             ai.privateFlags = ps.getPrivateFlags();
@@ -3561,7 +3556,7 @@
     @Override
     public int getPackageStartability(boolean safeMode, @NonNull String packageName, int callingUid,
             @UserIdInt int userId) {
-        final boolean userKeyUnlocked = StorageManager.isUserKeyUnlocked(userId);
+        final boolean ceStorageUnlocked = StorageManager.isCeStorageUnlocked(userId);
         final PackageStateInternal ps = getPackageStateInternal(packageName);
         if (ps == null || shouldFilterApplication(ps, callingUid, userId)
                 || !ps.getUserStateOrDefault(userId).isInstalled()) {
@@ -3576,7 +3571,7 @@
             return PackageManagerService.PACKAGE_STARTABILITY_FROZEN;
         }
 
-        if (!userKeyUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
+        if (!ceStorageUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
             return PackageManagerService.PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED;
         }
         return PackageManagerService.PACKAGE_STARTABILITY_OK;
@@ -4047,9 +4042,6 @@
         flags = updateFlagsForComponent(flags, userId);
         enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
                 false /* checkShell */, "get provider info");
-        if (callingUid != Process.SYSTEM_UID) {
-            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
-        }
         ParsedProvider p = mComponentResolver.getProvider(component);
         if (DEBUG_PACKAGE_INFO) Log.v(
                 TAG, "getProviderInfo " + component + ": " + p);
@@ -4600,6 +4592,7 @@
         flags = updateFlagsForApplication(flags, userId);
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
         final boolean listApex = (flags & MATCH_APEX) != 0;
+        final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
 
         enforceCrossUserPermission(
                 callingUid,
@@ -4611,7 +4604,7 @@
         ArrayList<ApplicationInfo> list;
         final ArrayMap<String, ? extends PackageStateInternal> packageStates =
                 getPackageStates();
-        if (listUninstalled) {
+        if (listUninstalled || listArchivedOnly) {
             list = new ArrayList<>(packageStates.size());
             for (PackageStateInternal ps : packageStates.values()) {
                 ApplicationInfo ai;
@@ -4623,6 +4616,11 @@
                     if (!listApex && ps.getPkg().isApex()) {
                         continue;
                     }
+                    PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+                    if (listArchivedOnly && !userState.isInstalled()
+                            && userState.getArchiveState() == null) {
+                        continue;
+                    }
                     if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
                         continue;
                     }
@@ -4679,9 +4677,6 @@
             int callingUid) {
         if (!mUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId);
-        if (callingUid != Process.SYSTEM_UID) {
-            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
-        }
         final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,
                 userId);
         boolean checkedGrants = false;
@@ -4794,13 +4789,6 @@
                 false /* checkShell */, "queryContentProviders");
         if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForComponent(flags, userId);
-
-        // Only if the service query is coming from the system process,
-        // it should be allowed to match quarantined components
-        if (callingUid != Process.SYSTEM_UID) {
-            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
-        }
-
         ArrayList<ProviderInfo> finalList = null;
         final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName,
                 metaDataKey, uid, flags, userId);
@@ -4938,7 +4926,7 @@
             int userId) {
         final int callingUid = Binder.getCallingUid();
         enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
-                false /* checkShell */, "isPackageSuspendedForUser for user " + userId);
+                false /* checkShell */, "when asking about packages for user " + userId);
         final PackageStateInternal ps = mSettings.getPackage(packageName);
         if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
             throw new IllegalArgumentException("Unknown target package: " + packageName);
@@ -4957,6 +4945,11 @@
     }
 
     @Override
+    public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) {
+        return getUserStageOrDefaultForUser(packageName, userId).isStopped();
+    }
+
+    @Override
     public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
             @UserIdInt int userId) {
         for (final PackageStateInternal packageState : getPackageStates().values()) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 8bd2982..a10bae9 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -223,11 +223,6 @@
                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
             }
 
-            // TODO(b/251903639): Do this when ART Service is used, or remove it from here.
-            if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
-                mPm.mArtManagerService.compileLayouts(packageState, pkg);
-            }
-
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
 
             String filter = getCompilerFilterForReason(pkgCompilationReason);
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 76203ac..9a0306b 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -961,6 +961,12 @@
     }
 
     @Override
+    public final boolean isPackageStoppedForUser(@NonNull String packageName,
+            @UserIdInt int userId) {
+        return snapshot().isPackageStoppedForUser(packageName, userId);
+    }
+
+    @Override
     @Deprecated
     public final boolean isSafeMode() {
         // allow instant applications
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index c6388e7..2951ef6 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.Flags.disallowSdkLibsToBeApps;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -58,7 +59,6 @@
 import static com.android.server.pm.PackageManagerService.MIN_INSTALLABLE_TARGET_SDK;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.POST_INSTALL;
-import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_FACTORY;
@@ -134,7 +134,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SELinux;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -166,7 +165,6 @@
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
-import com.android.server.pm.dex.ViewCompiler;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -221,7 +219,6 @@
     private final Context mContext;
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final PackageAbiHelper mPackageAbiHelper;
-    private final ViewCompiler mViewCompiler;
     private final SharedLibrariesImpl mSharedLibraries;
     private final PackageManagerServiceInjector mInjector;
     private final UpdateOwnershipHelper mUpdateOwnershipHelper;
@@ -245,7 +242,6 @@
         mContext = pm.mInjector.getContext();
         mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
         mPackageAbiHelper = pm.mInjector.getAbiHelper();
-        mViewCompiler = pm.mInjector.getViewCompiler();
         mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
         mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
     }
@@ -994,10 +990,11 @@
                         return;
                     }
                     final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
-                    if (!isApex) {
-                        createdAppId.put(packageName, optimisticallyRegisterAppId(request));
-                    } else {
+                    final boolean isSdkLibrary = packageToScan.isSdkLibrary();
+                    if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
                         request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
+                    } else {
+                        createdAppId.put(packageName, optimisticallyRegisterAppId(request));
                     }
                     versionInfos.put(packageName,
                             mPm.getSettingsVersionForPackage(packageToScan));
@@ -1267,10 +1264,9 @@
                     replace = true;
                     if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
                 }
-                final AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
-                if (replace && oldPackage != null) {
+                if (replace) {
                     // Prevent apps opting out from runtime permissions
-                    final int oldTargetSdk = oldPackage.getTargetSdkVersion();
+                    final int oldTargetSdk = ps.getTargetSdkVersion();
                     final int newTargetSdk = parsedPackage.getTargetSdkVersion();
                     if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
                             && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -1282,10 +1278,10 @@
                                         + " target SDK " + oldTargetSdk + " does.");
                     }
                     // Prevent persistent apps from being updated
-                    if (oldPackage.isPersistent()
+                    if (ps.isPersistent()
                             && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
                         throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
-                                "Package " + oldPackage.getPackageName() + " is a persistent app. "
+                                "Package " + pkgName + " is a persistent app. "
                                         + "Persistent apps are not updateable.");
                     }
                 }
@@ -1629,7 +1625,8 @@
                 synchronized (mPm.mLock) {
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG,
-                                "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
+                                "replacePackageLI: new=" + parsedPackage
+                                        + ", old=" + oldPackageState.getName());
                     }
 
                     ps = mPm.mSettings.getPackageLPr(pkgName11);
@@ -1666,7 +1663,7 @@
                     }
 
                     // don't allow a system upgrade unless the upgrade hash matches
-                    if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null
+                    if (oldPackageState.getRestrictUpdateHash() != null
                             && oldPackageState.isSystem()) {
                         final byte[] digestBytes;
                         try {
@@ -1682,12 +1679,13 @@
                             throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
                                     "Could not compute hash: " + pkgName11);
                         }
-                        if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
+                        if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) {
                             throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
                                     "New package fails restrict-update check: " + pkgName11);
                         }
                         // retain upgrade restriction
-                        parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
+                        parsedPackage.setRestrictUpdateHash(
+                                oldPackageState.getRestrictUpdateHash());
                     }
 
                     if (oldPackage != null) {
@@ -1787,7 +1785,7 @@
 
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
-                                + ", old=" + oldPackage);
+                                + ", old=" + oldPackageState.getName());
                     }
                     request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
                     request.setApexModuleName(oldPackageState.getApexModuleName());
@@ -1797,7 +1795,7 @@
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG,
                                 "replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
-                                        + oldPackage);
+                                        + oldPackageState.getName());
                     }
                 }
             } else { // new package install
@@ -2116,24 +2114,6 @@
                             // ignore; not possible for non-system app
                         }
                     }
-                    // Successfully deleted the old package; proceed with replace.
-                    // Update the in-memory copy of the previous code paths.
-                    PackageSetting ps1 = mPm.mSettings.getPackageLPr(
-                            installRequest.getExistingPackageName());
-                    if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP)
-                            == 0) {
-                        Set<String> oldCodePaths = ps1.getOldCodePaths();
-                        if (oldCodePaths == null) {
-                            oldCodePaths = new ArraySet<>();
-                        }
-                        if (oldPackage != null) {
-                            Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
-                            Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
-                        }
-                        ps1.setOldCodePaths(oldCodePaths);
-                    } else {
-                        ps1.setOldCodePaths(null);
-                    }
 
                     if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
                         PackageSetting ps2 = mPm.mSettings.getPackageLPr(
@@ -2394,12 +2374,6 @@
                 permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
                 mPm.mPermissionManager.onPackageInstalled(pkg, installRequest.getPreviousAppId(),
                         permissionParamsBuilder.build(), userId);
-                // Apply restricted settings on potentially dangerous packages.
-                if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
-                        || installRequest.getPackageSource()
-                        == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
-                    enableRestrictedSettings(pkgName, pkg.getUid());
-                }
             }
             installRequest.setName(pkgName);
             installRequest.setAppId(pkg.getUid());
@@ -2414,16 +2388,13 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    private void enableRestrictedSettings(String pkgName, int appId) {
+    private void enableRestrictedSettings(String pkgName, int appId, int userId) {
         final AppOpsManager appOpsManager = mPm.mContext.getSystemService(AppOpsManager.class);
-        final int[] allUsersList = mPm.mUserManager.getUserIds();
-        for (int userId : allUsersList) {
-            final int uid = UserHandle.getUid(userId, appId);
-            appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
-                    uid,
-                    pkgName,
-                    AppOpsManager.MODE_ERRORED);
-        }
+        final int uid = UserHandle.getUid(userId, appId);
+        appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                uid,
+                pkgName,
+                AppOpsManager.MODE_ERRORED);
     }
 
     /**
@@ -2536,13 +2507,6 @@
                             && !isApex;
 
             if (performDexopt) {
-                // Compile the layout resources.
-                if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
-                    mViewCompiler.compileLayouts(ps, pkg.getBaseApkPath());
-                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                }
-
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
 
                 // This mirrors logic from commitReconciledScanResultLocked, where the library files
@@ -2847,13 +2811,10 @@
                     mPm.mRequiredInstallerPackage,
                     /* packageSender= */ mPm, launchedForRestore, killApp, update, archived);
 
-
             // Work that needs to happen on first install within each user
-            if (firstUserIds.length > 0) {
-                for (int userId : firstUserIds) {
-                    mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
-                            userId);
-                }
+            for (int userId : firstUserIds) {
+                mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
+                        userId);
             }
 
             if (request.isAllNewUsers() && !update) {
@@ -2862,6 +2823,16 @@
                 mPm.notifyPackageChanged(packageName, request.getAppId());
             }
 
+            for (int userId : firstUserIds) {
+                // Apply restricted settings on potentially dangerous packages. Needs to happen
+                // after appOpsManager is notified of the new package
+                if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+                        || request.getPackageSource()
+                        == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+                    enableRestrictedSettings(packageName, request.getAppId(), userId);
+                }
+            }
+
             // Log current value of "unknown sources" setting
             EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
                     getUnknownSourcesSettings());
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 2d19282..7d822b5 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -22,6 +22,8 @@
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.os.Process.INVALID_UID;
 
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
 import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -32,6 +34,7 @@
 import android.app.AppOpsManager;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.DataLoaderType;
+import android.content.pm.Flags;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
@@ -56,6 +59,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
 
 final class InstallRequest {
@@ -147,6 +151,9 @@
     @NonNull
     private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY;
 
+    @NonNull
+    private ArrayList<String> mWarnings = new ArrayList<>();
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -658,6 +665,11 @@
         return mUpdateBroadcastInstantUserIds;
     }
 
+    @NonNull
+    public ArrayList<String> getWarnings() {
+        return mWarnings;
+    }
+
     public void setScanFlags(int scanFlags) {
         mScanFlags = scanFlags;
     }
@@ -855,6 +867,10 @@
         }
     }
 
+    public void addWarning(@NonNull String warning) {
+        mWarnings.add(warning);
+    }
+
     public void onPrepareStarted() {
         if (mPackageMetrics != null) {
             mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE);
@@ -904,22 +920,37 @@
     }
 
     public void onDexoptFinished(DexoptResult dexoptResult) {
-        if (mPackageMetrics == null) {
-            return;
-        }
-        mDexoptStatus = dexoptResult.getFinalStatus();
-        if (mDexoptStatus != DexoptResult.DEXOPT_PERFORMED) {
-            return;
-        }
-        long durationMillis = 0;
-        for (DexoptResult.PackageDexoptResult packageResult :
-                dexoptResult.getPackageDexoptResults()) {
-            for (DexoptResult.DexContainerFileDexoptResult fileResult :
-                    packageResult.getDexContainerFileDexoptResults()) {
-                durationMillis += fileResult.getDex2oatWallTimeMillis();
+        // Only report external profile warnings when installing from adb. The goal is to warn app
+        // developers if they have provided bad external profiles, so it's not beneficial to report
+        // those warnings in the normal app install workflow.
+        if (isInstallFromAdb() && Flags.useArtServiceV2()) {
+            var externalProfileErrors = new LinkedHashSet<String>();
+            for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) {
+                for (DexContainerFileDexoptResult fileResult :
+                        packageResult.getDexContainerFileDexoptResults()) {
+                    externalProfileErrors.addAll(fileResult.getExternalProfileErrors());
+                }
+            }
+            if (!externalProfileErrors.isEmpty()) {
+                addWarning("Error occurred during dexopt when processing external profiles:\n  "
+                        + String.join("\n  ", externalProfileErrors));
             }
         }
-        mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis);
+
+        // Report dexopt metrics.
+        if (mPackageMetrics != null) {
+            mDexoptStatus = dexoptResult.getFinalStatus();
+            if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) {
+                long durationMillis = 0;
+                for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) {
+                    for (DexContainerFileDexoptResult fileResult :
+                            packageResult.getDexContainerFileDexoptResults()) {
+                        durationMillis += fileResult.getDex2oatWallTimeMillis();
+                    }
+                }
+                mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis);
+            }
+        }
     }
 
     public void onInstallCompleted() {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0ebd33b..4ed3163 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -1128,14 +1128,6 @@
         throw new InstallerException("Invalid instruction set: " + instructionSet);
     }
 
-    public boolean compileLayouts(String apkPath, String packageName, String outDexFile, int uid) {
-        try {
-            return mInstalld.compileLayouts(apkPath, packageName, outDexFile, uid);
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
-
     /**
      * Returns the visibility of the optimized artifacts.
      *
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 11660a59..e857e05 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -56,6 +56,7 @@
 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;
@@ -65,6 +66,7 @@
 import android.content.pm.LauncherActivityInfoInternal;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
@@ -93,6 +95,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
@@ -107,19 +110,24 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.SizedInputStream;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.io.DataInputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -130,6 +138,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.function.BiConsumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -216,6 +226,7 @@
         private final ShortcutChangeHandler mShortcutChangeHandler;
 
         private final Handler mCallbackHandler;
+        private final ExecutorService mOnDumpExecutor = Executors.newSingleThreadExecutor();
 
         private PackageInstallerService mPackageInstallerService;
 
@@ -495,6 +506,9 @@
             if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) {
                 return false;
             }
+            if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) {
+                return true;
+            }
             if (mPackageManagerInternal.filterAppAccess(
                     packageName, Binder.getCallingUid(), userId)) {
                 return false;
@@ -506,18 +520,27 @@
 
         @Override
         public ParceledListSlice<LauncherActivityInfoInternal> getLauncherActivities(
-                String callingPackage, String packageName, UserHandle user) throws RemoteException {
+                String callingPackage, @Nullable String packageName, UserHandle user)
+                throws RemoteException {
             ParceledListSlice<LauncherActivityInfoInternal> launcherActivities =
-                    queryActivitiesForUser(callingPackage,
+                    queryActivitiesForUser(
+                            callingPackage,
                             new Intent(Intent.ACTION_MAIN)
                                     .addCategory(Intent.CATEGORY_LAUNCHER)
                                     .setPackage(packageName),
                             user);
-            if (Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 1) == 0) {
+            if (Flags.archiving()) {
+                launcherActivities =
+                        getActivitiesForArchivedApp(packageName, user, launcherActivities);
+            }
+            if (Settings.Global.getInt(
+                            mContext.getContentResolver(),
+                            Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED,
+                            1)
+                    == 0) {
                 return launcherActivities;
             }
-            if (launcherActivities == null) {
+            if (launcherActivities == null || launcherActivities.getList().isEmpty()) {
                 // Cannot access profile, so we don't even return any hidden apps.
                 return null;
             }
@@ -558,15 +581,16 @@
                     visiblePackages.add(info.getActivityInfo().packageName);
                 }
                 final List<ApplicationInfo> installedPackages =
-                        mPackageManagerInternal.getInstalledApplications(/* flags= */ 0,
-                                user.getIdentifier(), callingUid);
+                        mPackageManagerInternal.getInstalledApplications(
+                                /* flags= */ 0, user.getIdentifier(), callingUid);
                 for (ApplicationInfo applicationInfo : installedPackages) {
                     if (!visiblePackages.contains(applicationInfo.packageName)) {
                         if (!shouldShowSyntheticActivity(user, applicationInfo)) {
                             continue;
                         }
-                        LauncherActivityInfoInternal info = getHiddenAppActivityInfo(
-                                applicationInfo.packageName, callingUid, user);
+                        LauncherActivityInfoInternal info =
+                                getHiddenAppActivityInfo(
+                                        applicationInfo.packageName, callingUid, user);
                         if (info != null) {
                             result.add(info);
                         }
@@ -578,6 +602,23 @@
             }
         }
 
+        private ParceledListSlice<LauncherActivityInfoInternal> getActivitiesForArchivedApp(
+                @Nullable String packageName,
+                UserHandle user,
+                ParceledListSlice<LauncherActivityInfoInternal> launcherActivities) {
+            final List<LauncherActivityInfoInternal> archivedActivities =
+                    generateLauncherActivitiesForArchivedApp(packageName, user);
+            if (archivedActivities.isEmpty()) {
+                return launcherActivities;
+            }
+            if (launcherActivities == null) {
+                return new ParceledListSlice(archivedActivities);
+            }
+            List<LauncherActivityInfoInternal> result = launcherActivities.getList();
+            result.addAll(archivedActivities);
+            return new ParceledListSlice(result);
+        }
+
         private boolean shouldShowSyntheticActivity(UserHandle user, ApplicationInfo appInfo) {
             if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
                 return false;
@@ -643,23 +684,30 @@
                 return null;
             }
 
+            if (component == null || component.getPackageName() == null) {
+                // should not happen
+                return null;
+            }
+
             final int callingUid = injectBinderCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                final ActivityInfo activityInfo = mPackageManagerInternal.getActivityInfo(component,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                        callingUid, user.getIdentifier());
+                ActivityInfo activityInfo =
+                        mPackageManagerInternal.getActivityInfo(
+                                component,
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                                callingUid,
+                                user.getIdentifier());
                 if (activityInfo == null) {
-                    return null;
-                }
-                if (component == null || component.getPackageName() == null) {
-                    // should not happen
+                    if (Flags.archiving()) {
+                        return getMatchingArchivedAppActivityInfo(component, user);
+                    }
                     return null;
                 }
                 final IncrementalStatesInfo incrementalStatesInfo =
-                        mPackageManagerInternal.getIncrementalStatesInfo(component.getPackageName(),
-                                callingUid, user.getIdentifier());
+                        mPackageManagerInternal.getIncrementalStatesInfo(
+                                component.getPackageName(), callingUid, user.getIdentifier());
                 if (incrementalStatesInfo == null) {
                     // package does not exist; should not happen
                     return null;
@@ -670,6 +718,26 @@
             }
         }
 
+        private @Nullable LauncherActivityInfoInternal getMatchingArchivedAppActivityInfo(
+                @NonNull ComponentName component, UserHandle user) {
+            List<LauncherActivityInfoInternal> archivedActivities =
+                    generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
+            if (archivedActivities.isEmpty()) {
+                return null;
+            }
+            for (int i = 0; i < archivedActivities.size(); i++) {
+                if (archivedActivities.get(i).getComponentName().equals(component)) {
+                    return archivedActivities.get(i);
+                }
+            }
+            Slog.w(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "Expected archived app component name: %s" + " is not available!",
+                            component));
+            return null;
+        }
+
         @Override
         public ParceledListSlice getShortcutConfigActivities(
                 String callingPackage, String packageName, UserHandle user)
@@ -693,6 +761,100 @@
             }
         }
 
+        private boolean isPackageArchived(@NonNull String packageName, UserHandle user) {
+            return !getApplicationInfoForArchivedApp(packageName, user).isEmpty();
+        }
+
+        @NonNull
+        private List<LauncherActivityInfoInternal> generateLauncherActivitiesForArchivedApp(
+                @Nullable String packageName, UserHandle user) {
+            List<ApplicationInfo> applicationInfoList =
+                    (packageName == null)
+                            ? getApplicationInfoListForAllArchivedApps(user)
+                            : getApplicationInfoForArchivedApp(packageName, user);
+            List<LauncherActivityInfoInternal> launcherActivityList = new ArrayList<>();
+            for (int i = 0; i < applicationInfoList.size(); i++) {
+                ApplicationInfo applicationInfo = applicationInfoList.get(i);
+                PackageStateInternal packageState =
+                        mPackageManagerInternal.getPackageStateInternal(
+                                applicationInfo.packageName);
+                if (packageState == null) {
+                    continue;
+                }
+                ArchiveState archiveState =
+                        packageState.getUserStateOrDefault(user.getIdentifier()).getArchiveState();
+                if (archiveState == null) {
+                    Slog.w(
+                            TAG,
+                            TextUtils.formatSimple(
+                                    "Expected package: %s to be archived but missing ArchiveState"
+                                            + " in PackageState.",
+                                    applicationInfo.packageName));
+                    continue;
+                }
+                List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList =
+                        archiveState.getActivityInfos();
+                for (int j = 0; j < archiveActivityInfoList.size(); j++) {
+                    launcherActivityList.add(
+                            constructLauncherActivityInfoForArchivedApp(
+                                    user, applicationInfo, archiveActivityInfoList.get(j)));
+                }
+            }
+            return launcherActivityList;
+        }
+
+        private static LauncherActivityInfoInternal constructLauncherActivityInfoForArchivedApp(
+                UserHandle user,
+                ApplicationInfo applicationInfo,
+                ArchiveState.ArchiveActivityInfo archiveActivityInfo) {
+            ActivityInfo activityInfo = new ActivityInfo();
+            activityInfo.isArchived = applicationInfo.isArchived;
+            activityInfo.applicationInfo = applicationInfo;
+            activityInfo.packageName =
+                    archiveActivityInfo.getOriginalComponentName().getPackageName();
+            activityInfo.name = archiveActivityInfo.getOriginalComponentName().getClassName();
+            activityInfo.nonLocalizedLabel = archiveActivityInfo.getTitle();
+
+            return new LauncherActivityInfoInternal(
+                    activityInfo,
+                    new IncrementalStatesInfo(
+                            false /* isLoading */, 1 /* progress */, 0 /* loadingCompletedTime */),
+                    user);
+        }
+
+        @NonNull
+        private List<ApplicationInfo> getApplicationInfoListForAllArchivedApps(UserHandle user) {
+            final int callingUid = injectBinderCallingUid();
+            List<ApplicationInfo> installedApplicationInfoList =
+                    mPackageManagerInternal.getInstalledApplications(
+                            PackageManager.MATCH_ARCHIVED_PACKAGES,
+                            user.getIdentifier(),
+                            callingUid);
+            List<ApplicationInfo> archivedApplicationInfos = new ArrayList<>();
+            for (int i = 0; i < installedApplicationInfoList.size(); i++) {
+                ApplicationInfo installedApplicationInfo = installedApplicationInfoList.get(i);
+                if (installedApplicationInfo != null && installedApplicationInfo.isArchived) {
+                    archivedApplicationInfos.add(installedApplicationInfo);
+                }
+            }
+            return archivedApplicationInfos;
+        }
+
+        @NonNull
+        private List<ApplicationInfo> getApplicationInfoForArchivedApp(
+                @NonNull String packageName, UserHandle user) {
+            final int callingUid = injectBinderCallingUid();
+            ApplicationInfo applicationInfo = mPackageManagerInternal.getApplicationInfo(
+                    packageName,
+                    PackageManager.MATCH_ARCHIVED_PACKAGES,
+                    callingUid,
+                    user.getIdentifier());
+            if (applicationInfo == null || !applicationInfo.isArchived) {
+                return Collections.EMPTY_LIST;
+            }
+            return List.of(applicationInfo);
+        }
+
         private List<LauncherActivityInfoInternal> queryIntentLauncherActivities(
                 Intent intent, int callingUid, UserHandle user) {
             final List<ResolveInfo> apps = mPackageManagerInternal.queryIntentActivities(intent,
@@ -814,11 +976,17 @@
             final int callingUid = injectBinderCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                final PackageInfo info = mPackageManagerInternal.getPackageInfo(packageName,
+                long callingFlag =
                         PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                        callingUid, user.getIdentifier());
-                return info != null && info.applicationInfo.enabled;
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+                if (Flags.archiving()) {
+                    callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES;
+                }
+                final PackageInfo info =
+                        mPackageManagerInternal.getPackageInfo(
+                                packageName, callingFlag, callingUid, user.getIdentifier());
+                return info != null
+                        && (info.applicationInfo.enabled || info.applicationInfo.isArchived);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1284,7 +1452,18 @@
             if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
                 return false;
             }
-
+            if (Flags.archiving() && component != null && component.getPackageName() != null) {
+                List<LauncherActivityInfoInternal> archiveActivities =
+                        generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
+                if (!archiveActivities.isEmpty()) {
+                    for (int i = 0; i < archiveActivities.size(); i++) {
+                        if (archiveActivities.get(i).getComponentName().equals(component)) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            }
             final int callingUid = injectBinderCallingUid();
             final int state = mPackageManagerInternal.getComponentEnabledSetting(component,
                     callingUid, user.getIdentifier());
@@ -1371,6 +1550,25 @@
         }
 
         @Override
+        public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) {
+            // Only system launchers, which have access to recents should have access to this API.
+            // TODO(b/303803157): Add the new permission check if we decide to have one.
+            if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+                throw new SecurityException("Caller is not the recents app");
+            }
+            if (!canAccessProfile(user.getIdentifier(),
+                    "Can't access LauncherUserInfo for another user")) {
+                return null;
+            }
+            long ident = injectClearCallingIdentity();
+            try {
+                return mUserManagerInternal.getLauncherUserInfo(user.getIdentifier());
+            } finally {
+                injectRestoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public void startActivityAsUser(IApplicationThread caller, String callingPackage,
                 String callingFeatureId, ComponentName component, Rect sourceBounds,
                 Bundle opts, UserHandle user) throws RemoteException {
@@ -1512,7 +1710,7 @@
                     forEachViewCaptureWindow((fileName, is) -> {
                         try {
                             zipOs.putNextEntry(new ZipEntry("FS" + fileName));
-                            is.transferTo(zipOs);
+                            transferViewCaptureData(is, zipOs);
                             zipOs.closeEntry();
                         } catch (IOException e) {
                             getErrPrintWriter().write("Failed to output " + fileName
@@ -1553,8 +1751,9 @@
         private void dumpViewCaptureDataToWmTrace(@NonNull String fileName,
                 @NonNull InputStream is) {
             Path outPath = Paths.get(fileName);
-            try {
-                Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING);
+            try (OutputStream os = Files.newOutputStream(outPath, StandardOpenOption.CREATE,
+                    StandardOpenOption.TRUNCATE_EXISTING)) {
+                transferViewCaptureData(is, os);
                 Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS);
             } catch (IOException e) {
                 Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e);
@@ -1562,6 +1761,15 @@
         }
 
         /**
+         * Raw input stream reads hang on the final read when transferring data in via the pipe.
+         * The fix used below is to count and read the exact amount of bytes being sent.
+         */
+        private void transferViewCaptureData(InputStream is, OutputStream os) throws IOException {
+            DataInputStream dataInputStream = new DataInputStream(is);
+            new SizedInputStream(dataInputStream, dataInputStream.readInt()).transferTo(os);
+        }
+
+        /**
          * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data
          * to LauncherAppsService via the pipe's input provided. This data (as well as an output
          * file name) is provided to the consumer via an InputStream to output where it wants (for
@@ -1569,24 +1777,37 @@
          */
         private void forEachViewCaptureWindow(
                 @NonNull BiConsumer<String, InputStream> outputtingConsumer) {
-            for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
-                String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
-                String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
+            try {
+                // This multi-threading prevents ctrl-C command line command aborting from putting
+                // the mDumpCallbacks RemoteCallbackList in a bad Broadcast state. We need to wait
+                // for it to complete even though it is on a background thread.
+                mOnDumpExecutor.submit(() -> {
+                    try {
+                        for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
+                            String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
+                            String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
 
-                try {
-                    // Order is important here. OnDump needs to be called before the BiConsumer
-                    // accepts & starts blocking on reading the input stream.
-                    ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
-                    mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
+                            try {
+                                // Order is important here. OnDump needs to be called before the
+                                // BiConsumer accepts & starts blocking on reading the input stream.
+                                ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                                mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
 
-                    InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
-                    outputtingConsumer.accept(fileName, is);
-                    is.close();
-                } catch (Exception e) {
-                    Log.d(TAG, "failed to pipe view capture data", e);
-                }
+                                InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+                                        pipe[0]);
+                                outputtingConsumer.accept(fileName, is);
+                                is.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "failed to pipe view capture data", e);
+                            }
+                        }
+                    } finally {
+                        mDumpCallbacks.finishBroadcast();
+                    }
+                }).get();
+            } catch (InterruptedException | ExecutionException e) {
+                Log.e(TAG, "background work was interrupted", e);
             }
-            mDumpCallbacks.finishBroadcast();
         }
 
         @RequiresPermission(READ_FRAME_BUFFER)
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 148e0df..f5f5577 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -89,6 +89,21 @@
         if (packageState == null || packageState.getPkg() == null) {
             throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
         }
+        final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
+                mPm.mUserManager.getUserIds(), true);
+        final UserHandle userForMove;
+        if (installedUserIds.length > 0) {
+            userForMove = UserHandle.of(installedUserIds[0]);
+        } else {
+            throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST,
+                    "Package is not installed for any user");
+        }
+        for (int userId : installedUserIds) {
+            if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid,
+                    userId)) {
+                throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
+            }
+        }
         final AndroidPackage pkg = packageState.getPkg();
         if (packageState.isSystem()) {
             throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
@@ -134,8 +149,6 @@
         final String label = String.valueOf(pm.getApplicationLabel(
                 AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
         final int targetSdkVersion = pkg.getTargetSdkVersion();
-        final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
-                mPm.mUserManager.getUserIds(), true);
         final String fromCodePath;
         if (codeFile.getParentFile().getName().startsWith(
                 PackageManagerService.RANDOM_DIR_PREFIX)) {
@@ -183,7 +196,8 @@
         // If we're moving app data around, we need all the users unlocked
         if (moveCompleteApp) {
             for (int userId : installedUserIds) {
-                if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) {
+                if (StorageManager.isFileEncrypted()
+                        && !StorageManager.isCeStorageUnlocked(userId)) {
                     freezer.close();
                     throw new PackageManagerException(MOVE_FAILED_LOCKED_USER,
                             "User " + userId + " must be unlocked");
@@ -303,8 +317,8 @@
         final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
         final InstallingSession installingSession = new InstallingSession(origin, move,
                 installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
-                volumeUuid, user, packageAbiOverride,  PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
-                lite, mPm);
+                volumeUuid, userForMove, packageAbiOverride,
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
         installingSession.movePackage();
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index f59188e..42a97f7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -17,6 +17,8 @@
 package com.android.server.pm;
 
 import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.content.pm.ArchivedActivity.bytesFromBitmap;
+import static android.content.pm.ArchivedActivity.drawableToBitmap;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -27,6 +29,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.Context;
@@ -38,16 +41,16 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
 import android.content.pm.VersionedPackage;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelableException;
+import android.os.Process;
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -61,7 +64,6 @@
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -162,15 +164,13 @@
                         });
     }
 
-    /**
-     * Creates archived state for the package and user.
-     */
-    public CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
+    /** Creates archived state for the package and user. */
+    private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
             throws PackageManager.NameNotFoundException {
         PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
                 Binder.getCallingUid(), userId);
         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
-        verifyInstaller(responsibleInstallerPackage);
+        verifyInstaller(responsibleInstallerPackage, userId);
 
         List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
                 userId);
@@ -196,8 +196,12 @@
             for (int i = 0, size = mainActivities.length; i < size; ++i) {
                 var mainActivity = mainActivities[i];
                 Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
-                ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                        mainActivity.title, iconPath, null);
+                ArchiveActivityInfo activityInfo =
+                        new ArchiveActivityInfo(
+                                mainActivity.title,
+                                mainActivity.originalComponentName,
+                                iconPath,
+                                null);
                 archiveActivityInfos.add(activityInfo);
             }
 
@@ -211,12 +215,19 @@
     ArchiveState createArchiveStateInternal(String packageName, int userId,
             List<LauncherActivityInfo> mainActivities, String installerPackage)
             throws IOException {
+        final int iconSize = mContext.getSystemService(
+                ActivityManager.class).getLauncherLargeIconSize();
+
         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);
-            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                    mainActivity.getLabel().toString(), iconPath, null);
+            Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize);
+            ArchiveActivityInfo activityInfo =
+                    new ArchiveActivityInfo(
+                            mainActivity.getLabel().toString(),
+                            mainActivity.getComponentName(),
+                            iconPath,
+                            null);
             archiveActivityInfos.add(activityInfo);
         }
 
@@ -240,7 +251,7 @@
 
     @VisibleForTesting
     Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
-            @UserIdInt int userId, int index) throws IOException {
+            @UserIdInt int userId, int index, int iconSize) throws IOException {
         int iconResourceId = mainActivity.getActivityInfo().getIconResource();
         if (iconResourceId == 0) {
             // The app doesn't define an icon. No need to store anything.
@@ -248,7 +259,7 @@
         }
         File iconsDir = createIconsDir(userId);
         File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
-        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
+        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
         try (FileOutputStream out = new FileOutputStream(iconFile)) {
             // Note: Quality is ignored for PNGs.
             if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
@@ -260,27 +271,34 @@
         return iconFile.toPath();
     }
 
-    private void verifyInstaller(String installerPackage)
+    private void verifyInstaller(String installerPackage, int userId)
             throws PackageManager.NameNotFoundException {
         if (TextUtils.isEmpty(installerPackage)) {
             throw new PackageManager.NameNotFoundException("No installer found");
         }
-        if (!verifySupportsUnarchival(installerPackage)) {
+        // Allow shell for easier development.
+        if ((Binder.getCallingUid() != Process.SHELL_UID)
+                && !verifySupportsUnarchival(installerPackage, userId)) {
             throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
         }
     }
 
     /**
-     * @return true if installerPackage support unarchival:
-     * - has an action Intent.ACTION_UNARCHIVE_PACKAGE,
-     * - has permissions to install packages.
+     * Returns true if {@code installerPackage} supports unarchival being able to handle
+     * {@link Intent#ACTION_UNARCHIVE_PACKAGE}
      */
-    public boolean verifySupportsUnarchival(String installerPackage) {
-        // TODO(b/278553670) Check if installerPackage supports unarchival.
+    public boolean verifySupportsUnarchival(String installerPackage, int userId) {
         if (TextUtils.isEmpty(installerPackage)) {
             return false;
         }
-        return true;
+
+        Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE).setPackage(installerPackage);
+
+        ParceledListSlice<ResolveInfo> intentReceivers =
+                Binder.withCleanCallingIdentity(
+                        () -> mPm.queryIntentReceivers(mPm.snapshotComputer(),
+                                intent, /* resolvedType= */ null, /* flags= */ 0, userId));
+        return intentReceivers != null && !intentReceivers.getList().isEmpty();
     }
 
     void requestUnarchive(
@@ -531,29 +549,6 @@
         return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
     }
 
-    private static Bitmap drawableToBitmap(Drawable drawable) {
-        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);
-        } else {
-            bitmap =
-                    Bitmap.createBitmap(
-                            drawable.getIntrinsicWidth(),
-                            drawable.getIntrinsicHeight(),
-                            Bitmap.Config.ARGB_8888);
-        }
-        Canvas canvas = new Canvas(bitmap);
-        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
-        drawable.draw(canvas);
-        return bitmap;
-    }
-
     private static byte[] bytesFromBitmapFile(Path path) throws IOException {
         if (path == null) {
             return null;
@@ -563,18 +558,6 @@
         return bytesFromBitmap(BitmapFactory.decodeFile(path.toString()));
     }
 
-    private static byte[] bytesFromBitmap(Bitmap bitmap) throws IOException {
-        if (bitmap == null) {
-            return null;
-        }
-
-        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(
-                bitmap.getByteCount())) {
-            bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
-            return baos.toByteArray();
-        }
-    }
-
     /**
      * Creates serializable archived activities from existing ArchiveState.
      */
@@ -593,6 +576,7 @@
             }
             var archivedActivity = new ArchivedActivityParcel();
             archivedActivity.title = info.getTitle();
+            archivedActivity.originalComponentName = info.getOriginalComponentName();
             archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
             archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
                     info.getMonochromeIconBitmap());
@@ -610,8 +594,8 @@
     /**
      * Creates serializable archived activities from launcher activities.
      */
-    static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos)
-            throws IOException {
+    static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos,
+            int iconSize) throws IOException {
         if (infos == null || infos.isEmpty()) {
             throw new IllegalArgumentException("No launcher activities");
         }
@@ -624,9 +608,9 @@
             }
             var archivedActivity = new ArchivedActivityParcel();
             archivedActivity.title = info.getLabel().toString();
-            archivedActivity.iconBitmap =
-                    info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
-                            drawableToBitmap(info.getIcon(/* density= */ 0)));
+            archivedActivity.originalComponentName = info.getComponentName();
+            archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null :
+                    bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0), iconSize));
             // TODO(b/298452477) Handle monochrome icons.
             archivedActivity.monochromeIconBitmap = null;
             activities.add(archivedActivity);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1bb20b47..b9b5908 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
 import static android.os.Process.INVALID_UID;
 
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
@@ -42,6 +43,7 @@
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
@@ -621,6 +623,14 @@
     public int createSession(SessionParams params, String installerPackageName,
             String callingAttributionTag, int userId) {
         try {
+            if (params.dataLoaderParams != null
+                    && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("You need the "
+                        + "com.android.permission.USE_INSTALLER_V2 permission "
+                        + "to use a data loader");
+            }
+
             return createSessionInternal(params, installerPackageName, callingAttributionTag,
                     userId);
         } catch (IOException e) {
@@ -639,14 +649,6 @@
             throw new SecurityException("User restriction prevents installing");
         }
 
-        if (params.dataLoaderParams != null
-                && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
-                        != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the "
-                    + "com.android.permission.USE_INSTALLER_V2 permission "
-                    + "to use a data loader");
-        }
-
         // INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK
         // capability; ensure if this is set as the install reason the app has one of the necessary
         // signature permissions to perform the rollback.
@@ -1043,7 +1045,7 @@
         return false;
     }
 
-    private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
+    private PackageInstallerSession openSessionInternal(int sessionId) throws IOException {
         synchronized (mSessions) {
             final PackageInstallerSession session = mSessions.get(sessionId);
             if (!checkOpenSessionAccess(session)) {
@@ -1523,6 +1525,61 @@
         mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
     }
 
+    @Override
+    public void installPackageArchived(
+            @NonNull ArchivedPackageParcel archivedPackageParcel,
+            @NonNull SessionParams params,
+            @NonNull IntentSender statusReceiver,
+            @NonNull String installerPackageName,
+            @NonNull UserHandle userHandle) {
+        Objects.requireNonNull(params);
+        Objects.requireNonNull(archivedPackageParcel);
+        Objects.requireNonNull(statusReceiver);
+        Objects.requireNonNull(installerPackageName);
+        Objects.requireNonNull(userHandle);
+
+        final int callingUid = Binder.getCallingUid();
+        final int userId = userHandle.getIdentifier();
+        final Computer snapshot = mPm.snapshotComputer();
+        snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
+                "installPackageArchived");
+
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need the "
+                    + "com.android.permission.INSTALL_PACKAGES permission "
+                    + "to request archived package install");
+        }
+
+        params.installFlags |= PackageManager.INSTALL_ARCHIVED;
+        if (params.dataLoaderParams != null) {
+            throw new IllegalArgumentException(
+                    "Incompatible session param: dataLoaderParams has to be null");
+        }
+
+        params.setDataLoaderParams(
+                PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(null));
+        var metadata = PackageManagerShellCommandDataLoader.Metadata.forArchived(
+                archivedPackageParcel);
+
+        // Create and commit install archived session.
+        PackageInstallerSession session = null;
+        try {
+            var sessionId = createSessionInternal(params, installerPackageName,
+                    null /*installerAttributionTag*/, userId);
+            session = openSessionInternal(sessionId);
+            session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(),
+                    null /*signature*/);
+            session.commit(statusReceiver, false /*forTransfer*/);
+        } catch (IOException e) {
+            throw ExceptionUtils.wrap(e);
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
+    }
+
     private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
             int installerUid) {
         int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 6627039..5dc7dab 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2930,15 +2930,40 @@
      * @return a future that will be completed when the whole process is completed.
      */
     private CompletableFuture<Void> install() {
+        // `futures` either contains only one session (`this`) or contains one parent session
+        // (`this`) and n-1 child sessions.
         List<CompletableFuture<InstallResult>> futures = installNonStaged();
         CompletableFuture<InstallResult>[] arr = new CompletableFuture[futures.size()];
         return CompletableFuture.allOf(futures.toArray(arr)).whenComplete((r, t) -> {
             if (t == null) {
                 setSessionApplied();
+                var multiPackageWarnings = new ArrayList<String>();
+                if (isMultiPackage()) {
+                    // This is a parent session. Collect warnings from children.
+                    for (CompletableFuture<InstallResult> f : futures) {
+                        InstallResult result = f.join();
+                        if (result.session != this && result.extras != null) {
+                            ArrayList<String> childWarnings = result.extras.getStringArrayList(
+                                    PackageInstaller.EXTRA_WARNINGS);
+                            if (!ArrayUtils.isEmpty(childWarnings)) {
+                                multiPackageWarnings.addAll(childWarnings);
+                            }
+                        }
+                    }
+                }
                 for (CompletableFuture<InstallResult> f : futures) {
                     InstallResult result = f.join();
+                    Bundle extras = result.extras;
+                    if (isMultiPackage() && result.session == this
+                            && !multiPackageWarnings.isEmpty()) {
+                        if (extras == null) {
+                            extras = new Bundle();
+                        }
+                        extras.putStringArrayList(
+                                PackageInstaller.EXTRA_WARNINGS, multiPackageWarnings);
+                    }
                     result.session.dispatchSessionFinished(
-                            INSTALL_SUCCEEDED, "Session installed", result.extras);
+                            INSTALL_SUCCEEDED, "Session installed", extras);
                 }
             } else {
                 PackageManagerException e = (PackageManagerException) t.getCause();
@@ -3420,7 +3445,7 @@
             }
 
             if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
-                    getInstallSource().mInstallerPackageName)) {
+                    getInstallSource().mInstallerPackageName, userId)) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_SESSION_INVALID,
                         "Installer has to support unarchival in order to install archived "
@@ -5189,6 +5214,10 @@
             if (!TextUtils.isEmpty(existing)) {
                 fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
             }
+            ArrayList<String> warnings = extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS);
+            if (!ArrayUtils.isEmpty(warnings)) {
+                fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings);
+            }
         }
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 651845e..e749968 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -747,7 +747,7 @@
 
     @Override
     public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId,
-            @NonNull String recentCallingPackage, @NonNull String debugInfo) {
+            @Nullable String recentCallingPackage, @NonNull String debugInfo) {
         mService.notifyComponentUsed(snapshot(), packageName, userId,
                 recentCallingPackage, debugInfo);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a99a0c0..c9303f2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -164,6 +165,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -213,7 +215,6 @@
 import com.android.server.pm.dex.ArtUtils;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.dex.ViewCompiler;
 import com.android.server.pm.local.PackageManagerLocalImpl;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.PackageParser2;
@@ -810,8 +811,6 @@
     private final DexManager mDexManager;
     private final DynamicCodeLogger mDynamicCodeLogger;
 
-    final ViewCompiler mViewCompiler;
-
     private final AtomicInteger mNextMoveId = new AtomicInteger();
     final MovePackageHelper.MoveCallbacks mMoveCallbacks;
 
@@ -1434,6 +1433,9 @@
                 break;
             }
         }
+        if (!request.getWarnings().isEmpty()) {
+            extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings());
+        }
         return extras;
     }
 
@@ -1466,8 +1468,7 @@
             archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
             archPkg.versionCode = (int) longVersionCode;
 
-            // TODO(b/297916136): extract target sdk version.
-            archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+            archPkg.targetSdkVersion = ps.getTargetSdkVersion();
 
             // These get translated in flags important for user data management.
             archPkg.defaultToDeviceProtectedStorage = String.valueOf(
@@ -1482,11 +1483,14 @@
                 archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
                         archiveState);
             } else {
+                final int iconSize = mContext.getSystemService(
+                        ActivityManager.class).getLauncherLargeIconSize();
+
                 var mainActivities =
                         mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName,
                                 userId);
                 archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
-                        mainActivities);
+                        mainActivities, iconSize);
             }
         } catch (Exception e) {
             throw new IllegalArgumentException("Package does not have a main activity", e);
@@ -1666,7 +1670,6 @@
                 (i, pm) -> new ArtManagerService(i.getContext(), i.getInstaller(),
                         i.getInstallLock()),
                 (i, pm) -> ApexManager.getInstance(),
-                (i, pm) -> new ViewCompiler(i.getInstallLock(), i.getInstaller()),
                 (i, pm) -> (IncrementalManager)
                         i.getContext().getSystemService(Context.INCREMENTAL_SERVICE),
                 (i, pm) -> new DefaultAppProvider(() -> context.getSystemService(RoleManager.class),
@@ -1877,7 +1880,6 @@
         mProcessLoggingHandler = testParams.processLoggingHandler;
         mProtectedPackages = testParams.protectedPackages;
         mSeparateProcesses = testParams.separateProcesses;
-        mViewCompiler = testParams.viewCompiler;
         mRequiredVerifierPackages = testParams.requiredVerifierPackages;
         mRequiredInstallerPackage = testParams.requiredInstallerPackage;
         mRequiredUninstallerPackage = testParams.requiredUninstallerPackage;
@@ -2040,7 +2042,6 @@
         mBackgroundDexOptService = injector.getBackgroundDexOptService();
         mArtManagerService = injector.getArtManagerService();
         mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
-        mViewCompiler = injector.getViewCompiler();
         mSharedLibraries = mInjector.getSharedLibrariesImpl();
         mBackgroundHandler = injector.getBackgroundHandler();
 
@@ -3356,7 +3357,7 @@
         UserManagerInternal umInternal = mInjector.getUserManagerInternal();
         StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
         final int flags;
-        if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
+        if (StorageManager.isCeStorageUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
             flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
         } else if (umInternal.isUserRunning(userId)) {
             flags = StorageManager.FLAG_STORAGE_DE;
@@ -4498,6 +4499,7 @@
             boolean stopped, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return;
         final int callingUid = Binder.getCallingUid();
+        boolean wasStopped = false;
         if (snapshot.getInstantAppPackageName(callingUid) == null) {
             final int permission = mContext.checkCallingOrSelfPermission(
                     Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
@@ -4519,6 +4521,7 @@
                     ? null : packageState.getUserStateOrDefault(userId);
             if (packageState != null && packageUserState.isStopped() != stopped) {
                 boolean wasNotLaunched = packageUserState.isNotLaunched();
+                wasStopped = packageUserState.isStopped();
                 commitPackageStateMutation(null, packageName, state -> {
                     PackageUserStateWrite userState = state.userState(userId);
                     userState.setStopped(stopped);
@@ -4550,11 +4553,30 @@
                     ah.setHibernatingGlobally(packageName, false);
                 }
             });
+            // Send UNSTOPPED broadcast if necessary
+            if (wasStopped && Flags.stayStopped()) {
+                final PackageManagerInternal pmi =
+                        mInjector.getLocalService(PackageManagerInternal.class);
+                final int [] userIds = resolveUserIds(userId);
+                final SparseArray<int[]> broadcastAllowList =
+                        snapshotComputer().getVisibilityAllowLists(packageName, userIds);
+                final Bundle extras = new Bundle();
+                extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
+                extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+                extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
+                mHandler.post(() -> {
+                    mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
+                            packageName, extras,
+                            Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
+                            userIds, null, broadcastAllowList, null,
+                            null);
+                });
+            }
         }
     }
 
     void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName,
-            @UserIdInt int userId, @NonNull String recentCallingPackage,
+            @UserIdInt int userId, @Nullable String recentCallingPackage,
             @NonNull String debugInfo) {
         synchronized (mLock) {
             final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
@@ -6111,8 +6133,16 @@
             final Computer snapshot = snapshotComputer();
             enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
                     "setPackagesSuspendedAsUser");
-            boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
-                    && Flags.quarantinedEnabled();
+            boolean quarantined = false;
+            if (Flags.quarantinedEnabled()) {
+                if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
+                    quarantined = true;
+                } else if (FeatureFlagUtils.isEnabled(mContext,
+                        SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
+                    final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
+                    quarantined = callingPackage.equals(wellbeingPkg);
+                }
+            }
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
                     appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
                     false /* forQuietMode */, quarantined);
@@ -6926,6 +6956,26 @@
         public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) {
             return mInstallerService.getHistoricalSessions(userId);
         }
+
+        @Override
+        public void sendPackageRestartedBroadcast(@NonNull String packageName,
+                int uid, @Intent.Flags int flags) {
+            final int userId = UserHandle.getUserId(uid);
+            final int [] userIds = resolveUserIds(userId);
+            final SparseArray<int[]> broadcastAllowList =
+                    snapshotComputer().getVisibilityAllowLists(packageName, userIds);
+            final Bundle extras = new Bundle();
+            extras.putInt(Intent.EXTRA_UID, uid);
+            extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+            extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
+            mHandler.post(() -> {
+                mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
+                        packageName, extras,
+                        flags, null, null,
+                        userIds, null, broadcastAllowList, null,
+                        null);
+            });
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 5b770aab..ebf1c04 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -32,7 +32,6 @@
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.dex.ViewCompiler;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -112,7 +111,6 @@
     private final Singleton<ArtManagerService>
             mArtManagerServiceProducer;
     private final Singleton<ApexManager> mApexManagerProducer;
-    private final Singleton<ViewCompiler> mViewCompilerProducer;
     private final Singleton<IncrementalManager>
             mIncrementalManagerProducer;
     private final Singleton<DefaultAppProvider>
@@ -164,7 +162,6 @@
             Producer<DynamicCodeLogger> dynamicCodeLoggerProducer,
             Producer<ArtManagerService> artManagerServiceProducer,
             Producer<ApexManager> apexManagerProducer,
-            Producer<ViewCompiler> viewCompilerProducer,
             Producer<IncrementalManager> incrementalManagerProducer,
             Producer<DefaultAppProvider> defaultAppProviderProducer,
             Producer<DisplayMetrics> displayMetricsProducer,
@@ -214,7 +211,6 @@
         mArtManagerServiceProducer = new Singleton<>(
                 artManagerServiceProducer);
         mApexManagerProducer = new Singleton<>(apexManagerProducer);
-        mViewCompilerProducer = new Singleton<>(viewCompilerProducer);
         mIncrementalManagerProducer = new Singleton<>(
                 incrementalManagerProducer);
         mDefaultAppProviderProducer = new Singleton<>(
@@ -339,10 +335,6 @@
         return mApexManagerProducer.get(this, mPackageManager);
     }
 
-    public ViewCompiler getViewCompiler() {
-        return mViewCompilerProducer.get(this, mPackageManager);
-    }
-
     public Handler getBackgroundHandler() {
         return mBackgroundHandler;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index ca57209..9428ef6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -34,7 +34,6 @@
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.dex.ViewCompiler;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -93,7 +92,6 @@
     public @Nullable String systemTextClassifierPackage;
     public @Nullable String overlayConfigSignaturePackage;
     public @NonNull String requiredSdkSandboxPackage;
-    public ViewCompiler viewCompiler;
     public @Nullable String retailDemoPackage;
     public @Nullable String recentsPackage;
     public @Nullable String ambientContextDetectionPackage;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3a9272d..7264e2e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4397,10 +4397,21 @@
             session.commit(receiver.getIntentSender());
             if (!session.isStaged()) {
                 final Intent result = receiver.getResult();
-                final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                        PackageInstaller.STATUS_FAILURE);
+                int status = result.getIntExtra(
+                        PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+                List<String> warnings =
+                        result.getStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS);
                 if (status == PackageInstaller.STATUS_SUCCESS) {
-                    if (logSuccess) {
+                    if (!ArrayUtils.isEmpty(warnings)) {
+                        // Don't start the output string with "Success" because that will make adb
+                        // treat this as a success.
+                        for (String warning : warnings) {
+                            pw.println("Warning: " + warning);
+                        }
+                        // Treat warnings as failure to draw app developers' attention.
+                        status = PackageInstaller.STATUS_FAILURE;
+                        pw.println("Completed with warning(s)");
+                    } else if (logSuccess) {
                         pw.println("Success");
                     }
                 } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 9e7f043..a09e713 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -304,10 +304,6 @@
         public boolean onPrepareImage(@NonNull Collection<InstallationFile> addedFiles,
                 @NonNull Collection<String> removedFiles) {
             ShellCommand shellCommand = lookupShellCommand(mParams.getArguments());
-            if (shellCommand == null) {
-                Slog.e(TAG, "Missing shell command.");
-                return false;
-            }
             try {
                 for (InstallationFile file : addedFiles) {
                     Metadata metadata = Metadata.fromByteArray(file.getMetadata());
@@ -317,11 +313,19 @@
                     }
                     switch (metadata.getMode()) {
                         case Metadata.STDIN: {
+                            if (shellCommand == null) {
+                                Slog.e(TAG, "Missing shell command for Metadata.STDIN.");
+                                return false;
+                            }
                             final ParcelFileDescriptor inFd = getStdInPFD(shellCommand);
                             mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd);
                             break;
                         }
                         case Metadata.LOCAL_FILE: {
+                            if (shellCommand == null) {
+                                Slog.e(TAG, "Missing shell command for Metadata.LOCAL_FILE.");
+                                return false;
+                            }
                             ParcelFileDescriptor incomingFd = null;
                             try {
                                 final String filePath = new String(metadata.getData(),
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2e60064..3cf5481 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -89,16 +90,14 @@
     private static class Booleans {
         @IntDef({
                 INSTALL_PERMISSION_FIXED,
-                DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
                 UPDATE_AVAILABLE,
                 FORCE_QUERYABLE_OVERRIDE
         })
         public @interface Flags {
         }
         private static final int INSTALL_PERMISSION_FIXED = 1;
-        private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
-        private static final int UPDATE_AVAILABLE = 1 << 2;
-        private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
+        private static final int UPDATE_AVAILABLE = 1 << 1;
+        private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
     }
     private int mBooleans;
 
@@ -122,10 +121,6 @@
     @Nullable
     private Map<String, Set<String>> mimeGroups;
 
-    @Deprecated
-    @Nullable
-    private Set<String> mOldCodePaths;
-
     @Nullable
     private String[] usesSdkLibraries;
 
@@ -217,6 +212,11 @@
     @Nullable
     private String mAppMetadataFilePath;
 
+    private int mTargetSdkVersion;
+
+    @Nullable
+    private byte[] mRestrictUpdateHash;
+
     /**
      * Snapshot support.
      */
@@ -232,34 +232,16 @@
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public PackageSetting(String name, String realName, @NonNull File path,
-            String legacyNativeLibraryPath, String primaryCpuAbi,
-            String secondaryCpuAbi, String cpuAbiOverride,
-            long longVersionCode, int pkgFlags, int pkgPrivateFlags,
-            int sharedUserAppId,
-            String[] usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
-            String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Map<String, Set<String>> mimeGroups,
-            @NonNull UUID domainSetId) {
+    public PackageSetting(@NonNull String name, @Nullable String realName, @NonNull File path,
+                          int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId) {
         super(pkgFlags, pkgPrivateFlags);
         this.mName = name;
         this.mRealName = realName;
-        this.usesSdkLibraries = usesSdkLibraries;
-        this.usesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
-        this.usesStaticLibraries = usesStaticLibraries;
-        this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
         this.mPath = path;
         this.mPathString = path.toString();
-        this.legacyNativeLibraryPath = legacyNativeLibraryPath;
-        this.mPrimaryCpuAbi = primaryCpuAbi;
-        this.mSecondaryCpuAbi = secondaryCpuAbi;
-        this.mCpuAbiOverride = cpuAbiOverride;
-        this.versionCode = longVersionCode;
         this.signatures = new PackageSignatures();
         this.installSource = InstallSource.EMPTY;
-        this.mSharedUserAppId = sharedUserAppId;
-        mDomainSetId = domainSetId;
-        copyMimeGroups(mimeGroups);
+        this.mDomainSetId = domainSetId;
         mSnapshot = makeCache();
     }
 
@@ -512,13 +494,6 @@
         return this;
     }
 
-    public PackageSetting setDefaultToDeviceProtectedStorage(
-            boolean defaultToDeviceProtectedStorage) {
-        setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, defaultToDeviceProtectedStorage);
-        onChanged();
-        return this;
-    }
-
     @Override
     public boolean isExternalStorage() {
         return (getFlags() & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
@@ -530,9 +505,22 @@
         return this;
     }
 
-    public void setSharedUserAppId(int sharedUserAppId) {
+    public PackageSetting setSharedUserAppId(int sharedUserAppId) {
         mSharedUserAppId = sharedUserAppId;
         onChanged();
+        return this;
+    }
+
+    public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
+        mTargetSdkVersion = targetSdkVersion;
+        onChanged();
+        return this;
+    }
+
+    public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) {
+        mRestrictUpdateHash = restrictUpdateHash;
+        onChanged();
+        return this;
     }
 
     @Override
@@ -552,7 +540,7 @@
                 + " " + mName + "/" + mAppId + "}";
     }
 
-    protected void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
+    private void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
         if (newMimeGroups == null) {
             mimeGroups = null;
             return;
@@ -701,6 +689,9 @@
         categoryOverride = other.categoryOverride;
         mDomainSetId = other.mDomainSetId;
         mAppMetadataFilePath = other.mAppMetadataFilePath;
+        mTargetSdkVersion = other.mTargetSdkVersion;
+        mRestrictUpdateHash = other.mRestrictUpdateHash == null
+                ? null : other.mRestrictUpdateHash.clone();
 
         usesSdkLibraries = other.usesSdkLibraries != null
                 ? Arrays.copyOf(other.usesSdkLibraries,
@@ -727,15 +718,6 @@
             }
         }
 
-        if (mOldCodePaths != null) {
-            if (other.mOldCodePaths != null) {
-                mOldCodePaths.clear();
-                mOldCodePaths.addAll(other.mOldCodePaths);
-            } else {
-                mOldCodePaths = null;
-            }
-        }
-
         copyMimeGroups(other.mimeGroups);
         pkgState.updateFrom(other.pkgState);
         onChanged();
@@ -1201,6 +1183,9 @@
             long activityInfoToken = proto.start(
                     PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
             proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
+            proto.write(
+                    ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME,
+                    activityInfo.getOriginalComponentName().flattenToString());
             if (activityInfo.getIconBitmap() != null) {
                 proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
                         activityInfo.getIconBitmap().toAbsolutePath().toString());
@@ -1220,7 +1205,8 @@
     /**
      * @see #mPath
      */
-    PackageSetting setPath(@NonNull File path) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public PackageSetting setPath(@NonNull File path) {
         this.mPath = path;
         this.mPathString = path.toString();
         onChanged();
@@ -1421,15 +1407,11 @@
         return this;
     }
 
-    public PackageSetting setMimeGroups(@NonNull Map<String, Set<String>> mimeGroups) {
-        this.mimeGroups = mimeGroups;
-        onChanged();
-        return this;
-    }
-
-    public PackageSetting setOldCodePaths(Set<String> oldCodePaths) {
-        mOldCodePaths = oldCodePaths;
-        onChanged();
+    public PackageSetting setMimeGroups(@Nullable Map<String, Set<String>> mimeGroups) {
+        if (mimeGroups != null) {
+            copyMimeGroups(mimeGroups);
+            onChanged();
+        }
         return this;
     }
 
@@ -1569,7 +1551,12 @@
      */
     @Override
     public boolean isDefaultToDeviceProtectedStorage() {
-        return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
+        return (getPrivateFlags() & PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
+    }
+
+    @Override
+    public boolean isPersistent() {
+        return (getFlags() & ApplicationInfo.FLAG_PERSISTENT) != 0;
     }
 
 
@@ -1587,11 +1574,6 @@
     //@formatter:off
 
 
-    @DataClass.Generated.Member
-    public @Deprecated @Nullable Set<String> getOldCodePaths() {
-        return mOldCodePaths;
-    }
-
     /**
      * The path under which native libraries have been unpacked. This path is
      * always derived at runtime, and is only stored here for cleanup when a
@@ -1714,11 +1696,21 @@
         return mAppMetadataFilePath;
     }
 
+    @DataClass.Generated.Member
+    public int getTargetSdkVersion() {
+        return mTargetSdkVersion;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable byte[] getRestrictUpdateHash() {
+        return mRestrictUpdateHash;
+    }
+
     @DataClass.Generated(
-            time = 1694196905013L,
+            time = 1698188444364L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic  com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index da14397..203e1de 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -517,12 +517,6 @@
         if (!mUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
 
-        // Only if the service query is coming from the system process,
-        // it should be allowed to match quarantined components
-        if (callingUid != Process.SYSTEM_UID) {
-            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
-        }
-
         final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);
         flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
                 false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0cac790..7ea9e3f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,8 @@
                     UserManagerService.getInstance(), usesSdkLibraries,
                     parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
                     parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
-                    newDomainSetId);
+                    newDomainSetId,
+                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
         } else {
             // make a deep copy to avoid modifying any existing system state.
             pkgSetting = new PackageSetting(pkgSetting);
@@ -240,7 +241,8 @@
                     UserManagerService.getInstance(),
                     usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
-                    parsedPackage.getMimeGroups(), newDomainSetId);
+                    parsedPackage.getMimeGroups(), newDomainSetId,
+                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
         }
 
         if (createNewPackage && originalPkgSetting != null) {
@@ -462,8 +464,6 @@
                             + " to " + volumeUuid);
             pkgSetting.setVolumeUuid(volumeUuid);
         }
-        pkgSetting.setDefaultToDeviceProtectedStorage(
-                parsedPackage.isDefaultToDeviceProtectedStorage());
 
         SharedLibraryInfo sdkLibraryInfo = null;
         if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6e3b538..440823c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -368,6 +368,7 @@
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
     private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+    private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name";
     private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
     private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
     private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
@@ -930,16 +931,23 @@
             sharedUserSetting.mDisabledPackages.remove(p);
         }
         p.getPkgState().setUpdatedSystemApp(false);
-        PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
-                p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbiLegacy(),
-                p.getSecondaryCpuAbiLegacy(), p.getCpuAbiOverride(),
-                p.getAppId(), p.getVersionCode(), p.getFlags(), p.getPrivateFlags(),
-                p.getUsesSdkLibraries(), p.getUsesSdkLibrariesVersionsMajor(),
-                p.getUsesStaticLibraries(), p.getUsesStaticLibrariesVersions(), p.getMimeGroups(),
-                mDomainVerificationManager.generateNewId());
+        PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
+                p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId());
         if (ret != null) {
+            ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
+            ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
+            ret.setSecondaryCpuAbi(p.getSecondaryCpuAbiLegacy());
+            ret.setCpuAbiOverride(p.getCpuAbiOverride());
+            ret.setLongVersionCode(p.getVersionCode());
+            ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
+            ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+            ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
+            ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
+            ret.setMimeGroups(p.getMimeGroups());
             ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
             ret.getPkgState().setUpdatedSystemApp(false);
+            ret.setTargetSdkVersion(p.getTargetSdkVersion());
+            ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
         }
         mDisabledSysPackages.remove(name);
         return ret;
@@ -960,13 +968,8 @@
         }
     }
 
-    PackageSetting addPackageLPw(String name, String realName, File codePath,
-            String legacyNativeLibraryPathString, String primaryCpuAbiString,
-            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc,
-            int pkgFlags, int pkgPrivateFlags, String[] usesSdkLibraries,
-            long[] usesSdkLibrariesVersions, String[] usesStaticLibraries,
-            long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups,
-            @NonNull UUID domainSetId) {
+    PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, int pkgFlags,
+                                 int pkgPrivateFlags, @NonNull UUID domainSetId) {
         PackageSetting p = mPackages.get(name);
         if (p != null) {
             if (p.getAppId() == uid) {
@@ -976,11 +979,8 @@
                     "Adding duplicate package, keeping first: " + name);
             return null;
         }
-        p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
-                primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
-                pkgPrivateFlags, 0 /*userId*/, usesSdkLibraries, usesSdkLibrariesVersions,
-                usesStaticLibraries, usesStaticLibrariesVersions, mimeGroups, domainSetId);
-        p.setAppId(uid);
+        p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
+                .setAppId(uid);
         if (mAppIds.registerExistingAppId(uid, p, name)) {
             mPackages.put(name, p);
             return p;
@@ -1060,7 +1060,8 @@
             boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
             String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
             String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
+            Set<String> mimeGroupNames, @NonNull UUID domainSetId,
+            int targetSdkVersion, byte[] restrictUpdatedHash) {
         final PackageSetting pkgSetting;
         if (originalPkg != null) {
             if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1080,20 +1081,29 @@
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     // Update new package state.
                     .setLastModifiedTime(codePath.lastModified())
-                    .setDomainSetId(domainSetId);
+                    .setDomainSetId(domainSetId)
+                    .setTargetSdkVersion(targetSdkVersion)
+                    .setRestrictUpdateHash(restrictUpdatedHash);
             pkgSetting.setFlags(pkgFlags)
                     .setPrivateFlags(pkgPrivateFlags);
         } else {
             int installUserId = installUser != null ? installUser.getIdentifier()
                     : UserHandle.USER_SYSTEM;
 
-            pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
-                    legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
-                    null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
-                    0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
-                    usesStaticLibraries, usesStaticLibrariesVersions,
-                    createMimeGroups(mimeGroupNames), domainSetId);
-            pkgSetting.setLastModifiedTime(codePath.lastModified());
+            pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, pkgFlags,
+                    pkgPrivateFlags, domainSetId)
+                    .setUsesSdkLibraries(usesSdkLibraries)
+                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesStaticLibraries(usesStaticLibraries)
+                    .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
+                    .setLegacyNativeLibraryPath(legacyNativeLibraryPath)
+                    .setPrimaryCpuAbi(primaryCpuAbi)
+                    .setSecondaryCpuAbi(secondaryCpuAbi)
+                    .setLongVersionCode(versionCode)
+                    .setMimeGroups(createMimeGroups(mimeGroupNames))
+                    .setTargetSdkVersion(targetSdkVersion)
+                    .setRestrictUpdateHash(restrictUpdatedHash)
+                    .setLastModifiedTime(codePath.lastModified());
             if (sharedUser != null) {
                 pkgSetting.setSharedUserAppId(sharedUser.mAppId);
             }
@@ -1206,7 +1216,8 @@
             int pkgPrivateFlags, @NonNull UserManagerService userManager,
             @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
-            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
+            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
+            int targetSdkVersion, byte[] restrictUpdatedHash)
                     throws PackageManagerException {
         final String pkgName = pkgSetting.getPackageName();
         if (sharedUser != null) {
@@ -1258,7 +1269,9 @@
         pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
                 .setSecondaryCpuAbi(secondaryCpuAbi)
                 .updateMimeGroups(mimeGroupNames)
-                .setDomainSetId(domainSetId);
+                .setDomainSetId(domainSetId)
+                .setTargetSdkVersion(targetSdkVersion)
+                .setRestrictUpdateHash(restrictUpdatedHash);
         // Update SDK library dependencies if needed.
         if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
                 && usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
@@ -2068,6 +2081,8 @@
             if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
                 String title = parser.getAttributeValue(null,
                         ATTR_ARCHIVE_ACTIVITY_TITLE);
+                String originalComponentName =
+                        parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME);
                 String iconAttribute = parser.getAttributeValue(null,
                         ATTR_ARCHIVE_ICON_PATH);
                 Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
@@ -2076,17 +2091,27 @@
                 Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
                         monochromeAttribute);
 
-                if (title == null || iconPath == null) {
-                    Slog.wtf(TAG,
-                            TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
-                                    TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+                if (title == null || originalComponentName == null || iconPath == null) {
+                    Slog.wtf(
+                            TAG,
+                            TextUtils.formatSimple(
+                                    "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s",
+                                    TAG_ARCHIVE_ACTIVITY_INFO,
+                                    ATTR_ARCHIVE_ACTIVITY_TITLE,
+                                    title,
+                                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+                                    originalComponentName,
                                     ATTR_ARCHIVE_ICON_PATH,
                                     iconPath));
                     continue;
                 }
 
                 activityInfos.add(
-                        new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+                        new ArchiveState.ArchiveActivityInfo(
+                                title,
+                                ComponentName.unflattenFromString(originalComponentName),
+                                iconPath,
+                                monochromeIconPath));
             }
         }
         return activityInfos;
@@ -2458,6 +2483,10 @@
         for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
             serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
             serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+            serializer.attribute(
+                    null,
+                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+                    activityInfo.getOriginalComponentName().flattenToString());
             if (activityInfo.getIconBitmap() != null) {
                 serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
                         activityInfo.getIconBitmap().toAbsolutePath().toString());
@@ -3038,6 +3067,11 @@
         serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
         serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
         serializer.attributeLong(null, "version", pkg.getVersionCode());
+        serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+        if (pkg.getRestrictUpdateHash() != null) {
+            serializer.attributeBytesBase64(null, "restrictUpdateHash",
+                    pkg.getRestrictUpdateHash());
+        }
         if (pkg.getLegacyNativeLibraryPath() != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
         }
@@ -3101,6 +3135,11 @@
         serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
         serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
         serializer.attributeLong(null, "version", pkg.getVersionCode());
+        serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+        if (pkg.getRestrictUpdateHash() != null) {
+            serializer.attributeBytesBase64(null, "restrictUpdateHash",
+                    pkg.getRestrictUpdateHash());
+        }
         if (!pkg.hasSharedUser()) {
             serializer.attributeInt(null, "userId", pkg.getAppId());
         } else {
@@ -3137,8 +3176,6 @@
         if (pkg.getVolumeUuid() != null) {
             serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
         }
-        serializer.attributeBoolean(null, "defaultToDeviceProtectedStorage",
-                pkg.isDefaultToDeviceProtectedStorage());
         if (pkg.getCategoryOverride() != ApplicationInfo.CATEGORY_UNDEFINED) {
             serializer.attributeInt(null, "categoryHint", pkg.getCategoryOverride());
         }
@@ -3833,6 +3870,9 @@
         }
 
         long versionCode = parser.getAttributeLong(null, "version", 0);
+        int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+        byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
+                null);
 
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
@@ -3845,10 +3885,15 @@
         // debug invalid entries. The actual logic for migrating to a new ID is done in other
         // methods that use DomainVerificationManagerInternal#generateNewId
         UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
-        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
-                legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
-                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserAppId*/, null, null, null,
-                null, null, domainSetId);
+        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), pkgFlags,
+                pkgPrivateFlags, domainSetId)
+                .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+                .setPrimaryCpuAbi(primaryCpuAbiStr)
+                .setSecondaryCpuAbi(secondaryCpuAbiStr)
+                .setCpuAbiOverride(cpuAbiOverrideStr)
+                .setLongVersionCode(versionCode)
+                .setTargetSdkVersion(targetSdkVersion)
+                .setRestrictUpdateHash(restrictUpdateHash);
         long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
         if (timeStamp == 0) {
             timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3927,7 +3972,6 @@
         String installInitiatingPackageName = null;
         boolean installInitiatorUninstalled = false;
         String volumeUuid = null;
-        boolean defaultToDeviceProtectedStorage = false;
         boolean updateAvailable = false;
         int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
         int pkgFlags = 0;
@@ -3942,6 +3986,8 @@
         long loadingCompletedTime = 0;
         UUID domainSetId;
         String appMetadataFilePath = null;
+        int targetSdkVersion = 0;
+        byte[] restrictUpdateHash = null;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -3965,6 +4011,8 @@
             }
 
             versionCode = parser.getAttributeLong(null, "version", 0);
+            targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+            restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null);
             installerPackageName = parser.getAttributeValue(null, "installer");
             installerPackageUid = parser.getAttributeInt(null, "installerUid", INVALID_UID);
             updateOwnerPackageName = parser.getAttributeValue(null, "updateOwner");
@@ -3977,8 +4025,6 @@
             installInitiatorUninstalled = parser.getAttributeBoolean(null,
                     "installInitiatorUninstalled", false);
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
-            defaultToDeviceProtectedStorage = parser.getAttributeBoolean(
-                    null, "defaultToDeviceProtectedStorage", false);
             categoryHint = parser.getAttributeInt(null, "categoryHint",
                     ApplicationInfo.CATEGORY_UNDEFINED);
             appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
@@ -4060,11 +4106,7 @@
                                 + parser.getPositionDescription());
             } else if (appId > 0) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
-                        legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
-                        cpuAbiOverrideString, appId, versionCode, pkgFlags, pkgPrivateFlags,
-                        null /* usesSdkLibraries */, null /* usesSdkLibraryVersions */,
-                        null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */,
-                        null /* mimeGroups */, domainSetId);
+                        appId, pkgFlags, pkgPrivateFlags, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
                     Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
                             + appId + " pkg=" + packageSetting);
@@ -4073,22 +4115,26 @@
                             + appId + " while parsing settings at "
                             + parser.getPositionDescription());
                 } else {
+                    packageSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr);
+                    packageSetting.setPrimaryCpuAbi(primaryCpuAbiString);
+                    packageSetting.setSecondaryCpuAbi(secondaryCpuAbiString);
+                    packageSetting.setCpuAbiOverride(cpuAbiOverrideString);
+                    packageSetting.setLongVersionCode(versionCode);
                     packageSetting.setLastModifiedTime(timeStamp);
                     packageSetting.setLastUpdateTime(lastUpdateTime);
                 }
             } else if (sharedUserAppId != 0) {
                 if (sharedUserAppId > 0) {
                     packageSetting = new PackageSetting(name.intern(), realName,
-                            new File(codePathStr), legacyNativeLibraryPathStr,
-                            primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
-                            versionCode, pkgFlags, pkgPrivateFlags, sharedUserAppId,
-                            null /* usesSdkLibraries */,
-                            null /* usesSdkLibrariesVersions */,
-                            null /* usesStaticLibraries */,
-                            null /* usesStaticLibraryVersions */,
-                            null /* mimeGroups */, domainSetId);
-                    packageSetting.setLastModifiedTime(timeStamp);
-                    packageSetting.setLastUpdateTime(lastUpdateTime);
+                            new File(codePathStr), pkgFlags, pkgPrivateFlags, domainSetId)
+                            .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+                            .setPrimaryCpuAbi(primaryCpuAbiString)
+                            .setSecondaryCpuAbi(secondaryCpuAbiString)
+                            .setCpuAbiOverride(cpuAbiOverrideString)
+                            .setLongVersionCode(versionCode)
+                            .setSharedUserAppId(sharedUserAppId)
+                            .setLastModifiedTime(timeStamp)
+                            .setLastUpdateTime(lastUpdateTime);
                     mPendingPackages.add(packageSetting);
                     if (PackageManagerService.DEBUG_SETTINGS)
                         Log.i(PackageManagerService.TAG, "Reading package " + name
@@ -4118,7 +4164,6 @@
                     installInitiatorUninstalled);
             packageSetting.setInstallSource(installSource)
                     .setVolumeUuid(volumeUuid)
-                    .setDefaultToDeviceProtectedStorage(defaultToDeviceProtectedStorage)
                     .setCategoryOverride(categoryHint)
                     .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
                     .setPrimaryCpuAbi(primaryCpuAbiString)
@@ -4127,7 +4172,9 @@
                     .setForceQueryableOverride(installedForceQueryable)
                     .setLoadingProgress(loadingProgress)
                     .setLoadingCompletedTime(loadingCompletedTime)
-                    .setAppMetadataFilePath(appMetadataFilePath);
+                    .setAppMetadataFilePath(appMetadataFilePath)
+                    .setTargetSdkVersion(targetSdkVersion)
+                    .setRestrictUpdateHash(restrictUpdateHash);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -4888,9 +4935,11 @@
         }
         pw.print(prefix); pw.print("  versionCode="); pw.print(ps.getVersionCode());
         if (pkg != null) {
-            pw.print(" minSdk="); pw.print(pkg.getMinSdkVersion());
-            pw.print(" targetSdk="); pw.println(pkg.getTargetSdkVersion());
-
+            pw.print(" minSdk=");
+            pw.print(pkg.getMinSdkVersion());
+        }
+        pw.print(" targetSdk="); pw.println(ps.getTargetSdkVersion());
+        if (pkg != null) {
             SparseIntArray minExtensionVersions = pkg.getMinExtensionVersions();
 
             pw.print(prefix); pw.print("  minExtensionVersions=[");
@@ -6400,17 +6449,26 @@
     }
 
     boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) {
+        ArrayList<PersistentPreferredActivity> removed = null;
         PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
         Iterator<PersistentPreferredActivity> it = ppir.filterIterator();
         boolean changed = false;
         while (it.hasNext()) {
             PersistentPreferredActivity ppa = it.next();
             if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) {
-                ppir.removeFilter(ppa);
-                changed = true;
-                break;
+                if (removed == null) {
+                    removed = new ArrayList<>();
+                }
+                removed.add(ppa);
             }
         }
+        if (removed != null) {
+            for (int i = 0; i < removed.size(); i++) {
+                PersistentPreferredActivity ppa = removed.get(i);
+                ppir.removeFilter(ppa);
+            }
+            changed = true;
+        }
         if (changed) {
             onChanged();
         }
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 9987867..585e2e4 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
 
@@ -951,10 +952,12 @@
             }
         }
         if (!pkg.getUsesSdkLibraries().isEmpty()) {
+            // Allow installation even if sdk-library dependency doesn't exist
+            boolean required = !sdkLibIndependence();
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
                     pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
-                    pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
-                    availablePackages, newLibraries);
+                    pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+                    usesLibraryInfos, availablePackages, newLibraries);
         }
         return usesLibraryInfos;
     }
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index c725cdc..70aa19a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -183,7 +183,7 @@
                 StorageManagerInternal.class);
         for (UserInfo user : mPm.mUserManager.getUsers(false /* includeDying */)) {
             final int flags;
-            if (StorageManager.isUserKeyUnlocked(user.id)
+            if (StorageManager.isCeStorageUnlocked(user.id)
                     && smInternal.isCeStoragePrepared(user.id)) {
                 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
             } else if (umInternal.isUserRunning(user.id)) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 29d99a73..e8cebef 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -120,8 +120,8 @@
             return packageNames;
         }
 
-        final SuspendParams newSuspendParams =
-                new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined);
+        final SuspendParams newSuspendParams = suspended
+                ? new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined) : null;
 
         final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
 
@@ -156,8 +156,8 @@
 
             final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
                     packageState.getUserStateOrDefault(userId).getSuspendParams();
-            SuspendParams oldSuspendParams = suspendParamsMap == null
-                    ? null : suspendParamsMap.get(packageName);
+            final SuspendParams oldSuspendParams = suspendParamsMap == null
+                    ? null : suspendParamsMap.get(callingPackage);
             boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
 
             if (suspended && !changed) {
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 04cd183..0e7ce2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.UserProperties;
 import android.graphics.Bitmap;
@@ -407,6 +408,11 @@
     public abstract @NonNull UserInfo[] getUserInfos();
 
     /**
+     * Gets a {@link LauncherUserInfo} for the given {@code userId}, or {@code null} if not found.
+     */
+    public abstract @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId);
+
+    /**
      * Sets all default cross profile intent filters between {@code parentUserId} and
      * {@code profileUserId}.
      */
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c64a8c6..3430bb4d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -75,13 +76,13 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.multiuser.Flags;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IProgressListener;
@@ -1335,18 +1336,6 @@
                 && user.profileGroupId == profile.profileGroupId);
     }
 
-    private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode,
-            boolean useManagedActions) {
-        Intent intent = new Intent();
-        intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions));
-        intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
-        intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle());
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier());
-        intent.addFlags(
-                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-        return intent;
-    }
-
     private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {
         return useManagedActions ?
                 enableQuietMode ?
@@ -1359,12 +1348,20 @@
 
     private void broadcastProfileAvailabilityChanges(UserInfo profileInfo,
             UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) {
-        Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode,
-                useManagedActions);
+        Intent availabilityIntent = new Intent();
+        availabilityIntent.setAction(
+                getAvailabilityIntentAction(enableQuietMode, useManagedActions));
+        availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
+        availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle());
+        availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE,
+                profileInfo.getUserHandle().getIdentifier());
         if (profileInfo.isManagedProfile()) {
             getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
                     availabilityIntent, parentHandle, /* requiresPermission= */ true);
         }
+        availabilityIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+
         // TODO(b/302708423): Restrict the apps that can receive these intents in case of a private
         //  profile.
         final Bundle options = new BroadcastOptions()
@@ -1430,7 +1427,7 @@
             }
             final boolean needToShowConfirmCredential = !dontAskCredential
                     && mLockPatternUtils.isSecure(userId)
-                    && (!hasUnifiedChallenge || !StorageManager.isUserKeyUnlocked(userId));
+                    && (!hasUnifiedChallenge || !StorageManager.isCeStorageUnlocked(userId));
             if (needToShowConfirmCredential) {
                 if (onlyIfCredentialNotRequired) {
                     return false;
@@ -1559,7 +1556,7 @@
         logQuietModeEnabled(userId, enableQuietMode, callingPackage);
 
         // Broadcast generic intents for all profiles
-        if (Flags.allowPrivateProfile()) {
+        if (android.os.Flags.allowPrivateProfile()) {
             broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
                     enableQuietMode, false);
         }
@@ -3798,6 +3795,8 @@
 
     @GuardedBy({"mPackagesLock"})
     private void readUserListLP() {
+        // Whether guest restrictions are present on userlist.xml
+        boolean guestRestrictionsArePresentOnUserListXml = false;
         try (ResilientAtomicFile file = getUserListFile()) {
             FileInputStream fin = null;
             try {
@@ -3847,6 +3846,7 @@
                                 }
                             }
                         } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
+                            guestRestrictionsArePresentOnUserListXml = true;
                             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                                     && type != XmlPullParser.END_TAG) {
                                 if (type == XmlPullParser.START_TAG) {
@@ -3865,6 +3865,7 @@
 
                 updateUserIds();
                 upgradeIfNecessaryLP();
+                updateUsersWithFeatureFlags(guestRestrictionsArePresentOnUserListXml);
             } catch (Exception e) {
                 // Remove corrupted file and retry.
                 file.failRead(fin, e);
@@ -3890,6 +3891,24 @@
     }
 
     /**
+     * Update any user formats or Xml data that need to be updated based on the current user state
+     * and the feature flag settings.
+     */
+    @GuardedBy({"mPackagesLock"})
+    private void updateUsersWithFeatureFlags(boolean guestRestrictionsArePresentOnUserListXml) {
+        // User Xml re-writes are required when guest restrictions are saved on userlist.xml but
+        // as per the feature flag it should be on the SYSTEM user's xml or guest restrictions
+        // are saved on SYSTEM user's xml but as per the flags it should not be saved there.
+        if (guestRestrictionsArePresentOnUserListXml
+                == Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+            for (int userId: getUserIds()) {
+                writeUserLP(getUserDataNoChecks(userId));
+            }
+            writeUserListLP();
+        }
+    }
+
+    /**
      * Version of {@link #upgradeIfNecessaryLP()} that takes in the userVersion for testing
      * purposes. For non-tests, use {@link #upgradeIfNecessaryLP()}.
      */
@@ -4406,9 +4425,24 @@
             UserRestrictionsUtils.writeRestrictions(serializer,
                     mBaseUserRestrictions.getRestrictions(userInfo.id), TAG_RESTRICTIONS);
 
-            UserRestrictionsUtils.writeRestrictions(serializer,
-                    mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
-                    TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
+            if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+                if (userInfo.id == UserHandle.USER_SYSTEM) {
+                    UserRestrictionsUtils.writeRestrictions(serializer,
+                            mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
+                            TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
+
+                    serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
+                    synchronized (mGuestRestrictions) {
+                        UserRestrictionsUtils.writeRestrictions(serializer, mGuestRestrictions,
+                                TAG_RESTRICTIONS);
+                    }
+                    serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+                }
+            } else {
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
+                        TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
+            }
 
             UserRestrictionsUtils.writeRestrictions(serializer,
                     mDevicePolicyUserRestrictions.getRestrictions(userInfo.id),
@@ -4484,12 +4518,15 @@
                 serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion);
                 serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
 
-                serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
-                synchronized (mGuestRestrictions) {
-                    UserRestrictionsUtils
-                            .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+                if (!Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+                    serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
+                    synchronized (mGuestRestrictions) {
+                        UserRestrictionsUtils
+                                .writeRestrictions(serializer, mGuestRestrictions,
+                                        TAG_RESTRICTIONS);
+                    }
+                    serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
                 }
-                serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
                 int[] userIdsToWrite;
                 synchronized (mUsersLock) {
                     userIdsToWrite = new int[mUsers.size()];
@@ -4633,6 +4670,19 @@
                     localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
                     globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+                } else if (TAG_GUEST_RESTRICTIONS.equals(tag)) {
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && type != XmlPullParser.END_TAG) {
+                        if (type == XmlPullParser.START_TAG) {
+                            if (parser.getName().equals(TAG_RESTRICTIONS)) {
+                                synchronized (mGuestRestrictions) {
+                                    UserRestrictionsUtils
+                                            .readRestrictions(parser, mGuestRestrictions);
+                                }
+                            }
+                            break;
+                        }
+                    }
                 } else if (TAG_ACCOUNT.equals(tag)) {
                     type = parser.next();
                     if (type == XmlPullParser.TEXT) {
@@ -5058,9 +5108,9 @@
                 }
             }
 
-            t.traceBegin("createUserKey");
+            t.traceBegin("createUserStorageKeys");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral());
             t.traceEnd();
 
             // Only prepare DE storage here.  CE storage will be prepared later, when the user is
@@ -5862,17 +5912,18 @@
     private void removeUserState(final @UserIdInt int userId) {
         Slog.i(LOG_TAG, "Removing user state of user " + userId);
 
-        // Cleanup lock settings.  This must happen before destroyUserKey(), since the user's DE
-        // storage must still be accessible for the lock settings state to be properly cleaned up.
+        // Cleanup lock settings.  This requires that the user's DE storage still be accessible, so
+        // this must happen before destroyUserStorageKeys().
         mLockPatternUtils.removeUser(userId);
 
         // Evict and destroy the user's CE and DE encryption keys.  At this point, the user's CE and
         // DE storage is made inaccessible, except to delete its contents.
         try {
-            mContext.getSystemService(StorageManager.class).destroyUserKey(userId);
+            mContext.getSystemService(StorageManager.class).destroyUserStorageKeys(userId);
         } catch (IllegalStateException e) {
             // This may be simply because the user was partially created.
-            Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);
+            Slog.i(LOG_TAG, "Destroying storage keys for user " + userId
+                    + " failed, continuing anyway", e);
         }
 
         // Cleanup package manager settings
@@ -7117,14 +7168,32 @@
         }
 
         @Override
+        public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) {
+            UserInfo userInfo;
+            synchronized (mUsersLock) {
+                userInfo = getUserInfoLU(userId);
+            }
+            if (userInfo != null) {
+                final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
+                final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
+                        userDetails.getName(),
+                        userInfo.serialNumber)
+                        .build();
+                return uiInfo;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
         public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
             }
-            // Special case, in the stopping/shutdown state user key can still be unlocked
+            // Special case: in the stopping/shutdown state, CE storage can still be unlocked.
             if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) {
-                return StorageManager.isUserKeyUnlocked(userId);
+                return StorageManager.isCeStorageUnlocked(userId);
             }
             return (state == UserState.STATE_RUNNING_UNLOCKING)
                     || (state == UserState.STATE_RUNNING_UNLOCKED);
@@ -7141,9 +7210,9 @@
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
             }
-            // Special case, in the stopping/shutdown state user key can still be unlocked
+            // Special case: in the stopping/shutdown state, CE storage can still be unlocked.
             if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) {
-                return StorageManager.isUserKeyUnlocked(userId);
+                return StorageManager.isCeStorageUnlocked(userId);
             }
             return state == UserState.STATE_RUNNING_UNLOCKED;
         }
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 85b60a0..29e0c35 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -295,6 +295,8 @@
                         .setCredentialShareableWithParent(false)
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                        .setHideInSettingsInQuietMode(true)
                         .setCrossProfileIntentFilterAccessControl(
                                 UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
                         .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index f4f03f4..ae47aa8 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -540,44 +540,6 @@
     }
 
     /**
-     * Compile layout resources in a given package.
-     */
-    public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) {
-        try {
-            if (ps.isPrivileged() || pkg.isUseEmbeddedDex()
-                    || pkg.isDefaultToDeviceProtectedStorage()) {
-                // Privileged apps prefer to load trusted code so they don't use compiled views.
-                // If the app is not privileged but prefers code integrity, also avoid compiling
-                // views.
-                // Also disable the view compiler for protected storage apps since there are
-                // selinux permissions required for writing to user_de.
-                return false;
-            }
-            final String packageName = pkg.getPackageName();
-            final String apkPath = pkg.getSplits().get(0).getPath();
-            final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
-            if (dataDir == null) {
-                // The app is not installed on the target user and doesn't have a data dir
-                return false;
-            }
-            final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
-            Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
-                    ") to " + outDexFile);
-            final long callingId = Binder.clearCallingIdentity();
-            try {
-                return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
-                        pkg.getUid());
-            } finally {
-                Binder.restoreCallingIdentity(callingId);
-            }
-        }
-        catch (Throwable e) {
-            Log.e("PackageManager", "Failed to compile layouts", e);
-            return false;
-        }
-    }
-
-    /**
      * Build the profiles names for all the package code paths (excluding resource only paths).
      * Return the map [code path -> profile name].
      */
diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
deleted file mode 100644
index 00269224..0000000
--- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.pm.dex;
-
-import android.os.Binder;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.pm.Installer;
-import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import java.io.File;
-
-public class ViewCompiler {
-    private final Object mInstallLock;
-    @GuardedBy("mInstallLock")
-    private final Installer mInstaller;
-
-    public ViewCompiler(Object installLock, Installer installer) {
-        mInstallLock = installLock;
-        mInstaller = installer;
-    }
-
-    public boolean compileLayouts(PackageStateInternal ps, String apkPath) {
-        try {
-            final String packageName = ps.getPackageName();
-            File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
-            if (dataDir == null) {
-                // The app is not installed on the target user and doesn't have a data dir
-                return false;
-            }
-            final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
-            Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
-                ") to " + outDexFile);
-            final long callingId = Binder.clearCallingIdentity();
-            try {
-                synchronized (mInstallLock) {
-                    return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
-                        ps.getAppId());
-                }
-            } finally {
-                Binder.restoreCallingIdentity(callingId);
-            }
-        } catch (Throwable e) {
-            Log.e("PackageManager", "Failed to compile layouts", e);
-            return false;
-        }
-    }
-}
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 d16a812..2ad8bcf 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -379,7 +379,7 @@
         ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
                 | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
                 | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
-        if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0
+        if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0
                 && state.isQuarantined()) {
             ai.enabled = false;
         } else  if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
@@ -444,7 +444,7 @@
 
         updateApplicationInfo(info, flags, state);
 
-        initForUser(info, pkg, userId);
+        initForUser(info, pkg, userId, state);
 
         // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
         PackageStateUnserialized pkgState = pkgSetting.getTransientState();
@@ -690,7 +690,7 @@
         info.splitDependencies = pkg.getSplitDependencies().size() == 0
                 ? null : pkg.getSplitDependencies();
 
-        initForUser(info, pkg, userId);
+        initForUser(info, pkg, userId, state);
 
         info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
         info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
@@ -1006,7 +1006,7 @@
     }
 
     private static void initForUser(ApplicationInfo output, AndroidPackage input,
-            @UserIdInt int userId) {
+            @UserIdInt int userId, PackageUserStateInternal state) {
         PackageImpl pkg = ((PackageImpl) input);
         String packageName = input.getPackageName();
         output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
@@ -1016,6 +1016,13 @@
             return;
         }
 
+        if (!state.isInstalled() && !state.dataExists()
+                && android.content.pm.Flags.nullableDataDir()) {
+            // The data dir has been deleted
+            output.dataDir = null;
+            return;
+        }
+
         // For performance reasons, all these paths are built as strings
         if (userId == UserHandle.USER_SYSTEM) {
             output.credentialProtectedDataDir =
@@ -1050,7 +1057,7 @@
     // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
     // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
     private static void initForUser(InstrumentationInfo output, AndroidPackage input,
-            @UserIdInt int userId) {
+            @UserIdInt int userId, PackageUserStateInternal state) {
         PackageImpl pkg = ((PackageImpl) input);
         String packageName = input.getPackageName();
         if ("android".equals(packageName)) {
@@ -1058,6 +1065,13 @@
             return;
         }
 
+        if (!state.isInstalled() && !state.dataExists()
+                && android.content.pm.Flags.nullableDataDir()) {
+            // The data dir has been deleted
+            output.dataDir = null;
+            return;
+        }
+
         // For performance reasons, all these paths are built as strings
         if (userId == UserHandle.USER_SYSTEM) {
             output.credentialProtectedDataDir =
@@ -1089,12 +1103,23 @@
         }
     }
 
-    @NonNull
+    /**
+     * Returns the data dir of the app for the target user. Return null if the app isn't installed
+     * on the target user and doesn't have a data dir on the target user.
+     */
+    @Nullable
     public static File getDataDir(PackageStateInternal ps, int userId) {
         if ("android".equals(ps.getPackageName())) {
             return Environment.getDataSystemDirectory();
         }
 
+        if (!ps.getUserStateOrDefault(userId).isInstalled()
+                && !ps.getUserStateOrDefault(userId).dataExists()
+                && android.content.pm.Flags.nullableDataDir()) {
+            // The app has been uninstalled for the user and the data dir has been deleted
+            return null;
+        }
+
         if (ps.isDefaultToDeviceProtectedStorage()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
index 4916a4a..1e40d44 100644
--- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -16,9 +16,11 @@
 
 package com.android.server.pm.pkg;
 
+import android.content.ComponentName;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.util.AnnotationValidations;
 import com.android.internal.util.DataClass;
 
 import java.nio.file.Path;
@@ -56,6 +58,10 @@
         @NonNull
         private final String mTitle;
 
+        /** The component name of the original activity (pre-archival). */
+        @NonNull
+        private final ComponentName mOriginalComponentName;
+
         /**
          * The path to the stored icon of the activity in the app's locale. Null if the app does
          * not define any icon (default icon would be shown on the launcher).
@@ -96,11 +102,13 @@
         @DataClass.Generated.Member
         public ArchiveActivityInfo(
                 @NonNull String title,
+                @NonNull ComponentName originalComponentName,
                 @Nullable Path iconBitmap,
                 @Nullable Path monochromeIconBitmap) {
             this.mTitle = title;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTitle);
+            this.mOriginalComponentName = originalComponentName;
+            AnnotationValidations.validate(NonNull.class, null, mTitle);
+            AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName);
             this.mIconBitmap = iconBitmap;
             this.mMonochromeIconBitmap = monochromeIconBitmap;
 
@@ -116,6 +124,14 @@
         }
 
         /**
+         * The component name of the original activity (pre-archival).
+            */
+        @DataClass.Generated.Member
+        public @NonNull ComponentName getOriginalComponentName() {
+            return mOriginalComponentName;
+        }
+
+        /**
          * The path to the stored icon of the activity in the app's locale. Null if the app does
          * not define any icon (default icon would be shown on the launcher).
          */
@@ -140,6 +156,7 @@
 
             return "ArchiveActivityInfo { " +
                     "title = " + mTitle + ", " +
+                    "originalComponentName = " + mOriginalComponentName + ", " +
                     "iconBitmap = " + mIconBitmap + ", " +
                     "monochromeIconBitmap = " + mMonochromeIconBitmap +
             " }";
@@ -159,6 +176,7 @@
             //noinspection PointlessBooleanExpression
             return true
                     && java.util.Objects.equals(mTitle, that.mTitle)
+                    && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName)
                     && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
                     && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
         }
@@ -171,6 +189,7 @@
 
             int _hash = 1;
             _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+            _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName);
             _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
             _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
             return _hash;
@@ -180,7 +199,8 @@
                 time = 1693590309015L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
-                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+                inputSignatures =
+                        "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
         @Deprecated
         private void __metadata() {}
 
@@ -224,11 +244,9 @@
             @NonNull List<ArchiveActivityInfo> activityInfos,
             @NonNull String installerTitle) {
         this.mActivityInfos = activityInfos;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mActivityInfos);
+        AnnotationValidations.validate(NonNull.class, null, mActivityInfos);
         this.mInstallerTitle = installerTitle;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mInstallerTitle);
+        AnnotationValidations.validate(NonNull.class, null, mInstallerTitle);
 
         // onConstructed(); // You can define this method to get a callback
     }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3f347e4..e7137bb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -434,4 +434,26 @@
      */
     @Nullable
     String getApexModuleName();
+
+    /**
+     * @see ApplicationInfo#FLAG_PERSISTENT
+     * @see R.styleable#AndroidManifestApplication_persistent
+     * @hide
+     */
+    boolean isPersistent();
+
+    /**
+     * @see ApplicationInfo#targetSdkVersion
+     * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+     * @hide
+     */
+    int getTargetSdkVersion();
+
+    /**
+     * @see R.styleable#AndroidManifestRestrictUpdate
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    byte[] getRestrictUpdateHash();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index 7b07e5b..cd3583b 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -16,9 +16,9 @@
 
 package com.android.server.pm.pkg;
 
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
 import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS;
 
 import android.annotation.NonNull;
 import android.content.pm.ComponentInfo;
@@ -147,7 +147,7 @@
             return true;
         }
 
-        if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) {
+        if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 86391c9..153238fa 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -17,7 +17,6 @@
 package com.android.server.pm.pkg;
 
 import android.annotation.Nullable;
-import android.content.pm.Flags;
 import android.content.pm.SuspendDialogInfo;
 import android.os.BaseBundle;
 import android.os.PersistableBundle;
@@ -142,8 +141,7 @@
         PersistableBundle readAppExtras = null;
         PersistableBundle readLauncherExtras = null;
 
-        final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false)
-                && Flags.quarantinedEnabled();
+        final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false);
 
         final int currentDepth = in.getDepth();
         int type;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f14941b..8240c47 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.Flags.disallowSdkLibsToBeApps;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -403,8 +404,9 @@
 
         try {
             final File baseApk = new File(lite.getBaseApkPath());
+            boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
             final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
-                    lite.getPath(), assetLoader, flags);
+                    lite.getPath(), assetLoader, flags, shouldSkipComponents);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -456,10 +458,11 @@
         final PackageLite lite = liteResult.getResult();
         final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
         try {
+            boolean shouldSkipComponents =  lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
             final ParseResult<ParsingPackage> result = parseBaseApk(input,
                     apkFile,
                     apkFile.getCanonicalPath(),
-                    assetLoader, flags);
+                    assetLoader, flags, shouldSkipComponents);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -594,7 +597,8 @@
     }
 
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
-            String codePath, SplitAssetLoader assetLoader, int flags) {
+            String codePath, SplitAssetLoader assetLoader, int flags,
+            boolean shouldSkipComponents) {
         final String apkPath = apkFile.getAbsolutePath();
 
         final String volumeUuid = getVolumeUuid(apkPath);
@@ -619,7 +623,7 @@
             final Resources res = new Resources(assets, mDisplayMetrics, null);
 
             ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
-                    parser, flags);
+                    parser, flags, shouldSkipComponents);
             if (result.isError()) {
                 return input.error(result.getErrorCode(),
                         apkPath + " (at " + parser.getPositionDescription() + "): "
@@ -719,11 +723,12 @@
      * @param res     The resources from which to resolve values
      * @param parser  The manifest parser
      * @param flags   Flags how to parse
+     * @param shouldSkipComponents If the package is a sdk-library
      * @return Parsed package or null on error.
      */
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
-            String codePath, Resources res, XmlResourceParser parser, int flags)
-            throws XmlPullParserException, IOException {
+            String codePath, Resources res, XmlResourceParser parser, int flags,
+            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
         final String splitName;
         final String pkgName;
 
@@ -751,7 +756,8 @@
             final ParsingPackage pkg = mCallback.startParsingPackage(
                     pkgName, apkPath, codePath, manifestArray, isCoreApp);
             final ParseResult<ParsingPackage> result =
-                    parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
+                    parseBaseApkTags(input, pkg, manifestArray, res, parser, flags,
+                            shouldSkipComponents);
             if (result.isError()) {
                 return result;
             }
@@ -987,10 +993,9 @@
                 return ParsingUtils.unknownTag("<application>", pkg, parser, input);
         }
     }
-
     private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
-            TypedArray sa, Resources res, XmlResourceParser parser, int flags)
-            throws XmlPullParserException, IOException {
+            TypedArray sa, Resources res, XmlResourceParser parser, int flags,
+            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
         ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
         if (sharedUserResult.isError()) {
             return sharedUserResult;
@@ -1027,7 +1032,8 @@
                     }
                 } else {
                     foundApp = true;
-                    result = parseBaseApplication(input, pkg, res, parser, flags);
+                    result = parseBaseApplication(input, pkg, res, parser, flags,
+                            shouldSkipComponents);
                 }
             } else {
                 result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
@@ -1972,8 +1978,8 @@
      * code moves around.
      */
     private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
-            ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
-            throws XmlPullParserException, IOException {
+            ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
+            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
         final String pkgName = pkg.getPackageName();
         int targetSdk = pkg.getTargetSdkVersion();
 
@@ -2213,6 +2219,9 @@
                     isActivity = true;
                     // fall-through
                 case "receiver":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     ParseResult<ParsedActivity> activityResult =
                             ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                                     res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/,
@@ -2232,6 +2241,9 @@
                     result = activityResult;
                     break;
                 case "service":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     ParseResult<ParsedService> serviceResult =
                             ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
                                     flags, sUseRoundIcon, null /*defaultSplitName*/,
@@ -2245,6 +2257,9 @@
                     result = serviceResult;
                     break;
                 case "provider":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     ParseResult<ParsedProvider> providerResult =
                             ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
                                     flags, sUseRoundIcon, null /*defaultSplitName*/,
@@ -2256,6 +2271,9 @@
                     result = providerResult;
                     break;
                 case "activity-alias":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
                             parser, sUseRoundIcon, null /*defaultSplitName*/,
                             input);
@@ -2414,7 +2432,7 @@
     /**
      * For parsing non-MainComponents. Main ones have an order and some special handling which is
      * done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources,
-     * XmlResourceParser, int)}.
+     * XmlResourceParser, int, boolean)}.
      */
     private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg,
             Resources res, XmlResourceParser parser, int flags)
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index d6e35e8..a33e353 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -347,8 +347,15 @@
                 UserHandle user = UserHandle.getUserHandleForUid(uid);
                 PermissionControllerManager manager = mPermControllerManagers.get(user);
                 if (manager == null) {
-                    manager = new PermissionControllerManager(
-                            getUserContext(getContext(), user), PermissionThread.getHandler());
+                    try {
+                        manager = new PermissionControllerManager(
+                                getUserContext(getContext(), user), PermissionThread.getHandler());
+                    } catch (IllegalArgumentException exception) {
+                        // There's a possible race condition when a user is being removed
+                        Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
+                                        + user, exception);
+                        return;
+                    }
                     mPermControllerManagers.put(user, manager);
                 }
                 manager.updateUserSensitiveForApp(uid);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3a6664a..077812b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@
 import android.os.FactoryTest;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeReason;
@@ -545,6 +546,7 @@
     int mLidKeyboardAccessibility;
     int mLidNavigationAccessibility;
     int mShortPressOnPowerBehavior;
+    private boolean mShouldEarlyShortPressOnPower;
     int mLongPressOnPowerBehavior;
     long mLongPressOnPowerAssistantTimeoutMs;
     int mVeryLongPressOnPowerBehavior;
@@ -715,6 +717,11 @@
     private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
 
     private class PolicyHandler extends Handler {
+
+        private PolicyHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -2166,10 +2173,12 @@
     static class Injector {
         private final Context mContext;
         private final WindowManagerFuncs mWindowManagerFuncs;
+        private final Looper mLooper;
 
-        Injector(Context context, WindowManagerFuncs funcs) {
+        Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
             mContext = context;
             mWindowManagerFuncs = funcs;
+            mLooper = looper;
         }
 
         Context getContext() {
@@ -2180,6 +2189,10 @@
             return mWindowManagerFuncs;
         }
 
+        Looper getLooper() {
+            return mLooper;
+        }
+
         AccessibilityShortcutController getAccessibilityShortcutController(
                 Context context, Handler handler, int initialUserId) {
             return new AccessibilityShortcutController(context, handler, initialUserId);
@@ -2208,7 +2221,7 @@
     /** {@inheritDoc} */
     @Override
     public void init(Context context, WindowManagerFuncs funcs) {
-        init(new Injector(context, funcs));
+        init(new Injector(context, funcs, Looper.myLooper()));
     }
 
     @VisibleForTesting
@@ -2284,7 +2297,7 @@
                     mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
         }
 
-        mHandler = new PolicyHandler();
+        mHandler = new PolicyHandler(injector.getLooper());
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
@@ -2466,7 +2479,7 @@
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
         mKeyguardDelegate = injector.getKeyguardServiceDelegate();
         initKeyCombinationRules();
-        initSingleKeyGestureRules();
+        initSingleKeyGestureRules(injector.getLooper());
         mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
     }
 
@@ -2639,6 +2652,9 @@
 
         @Override
         void onPress(long downTime) {
+            if (mShouldEarlyShortPressOnPower) {
+                return;
+            }
             powerPress(downTime, 1 /*count*/);
         }
 
@@ -2672,6 +2688,13 @@
         void onMultiPress(long downTime, int count) {
             powerPress(downTime, count);
         }
+
+        @Override
+        void onKeyUp(long eventTime, int count) {
+            if (mShouldEarlyShortPressOnPower && count == 1) {
+                powerPress(eventTime, 1 /*pressCount*/);
+            }
+        }
     }
 
     /**
@@ -2737,8 +2760,8 @@
         }
     }
 
-    private void initSingleKeyGestureRules() {
-        mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext);
+    private void initSingleKeyGestureRules(Looper looper) {
+        mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext, looper);
         mSingleKeyGestureDetector.addRule(new PowerKeyRule());
         if (hasLongPressOnBackBehavior()) {
             mSingleKeyGestureDetector.addRule(new BackKeyRule());
@@ -2901,6 +2924,9 @@
                     Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS,
                     mContext.getResources().getInteger(
                             com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior));
+            mShouldEarlyShortPressOnPower =
+                    mContext.getResources()
+                            .getBoolean(com.android.internal.R.bool.config_shortPressEarlyOnPower);
 
             mStylusButtonsEnabled = Settings.Secure.getIntForUser(resolver,
                     Secure.STYLUS_BUTTONS_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 5fc0637..047555a 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -40,6 +40,7 @@
     private static final int MSG_KEY_LONG_PRESS = 0;
     private static final int MSG_KEY_VERY_LONG_PRESS = 1;
     private static final int MSG_KEY_DELAYED_PRESS = 2;
+    private static final int MSG_KEY_UP = 3;
 
     private int mKeyPressCounter;
     private boolean mBeganFromNonInteractive = false;
@@ -144,6 +145,13 @@
          *  Callback when very long press has been detected.
          */
         void onVeryLongPress(long eventTime) {}
+        /**
+         * Callback executed upon each key up event that hasn't been processed by long press.
+         *
+         * @param eventTime the timestamp of this event.
+         * @param pressCount the number of presses detected leading up to this key up event.
+         */
+        void onKeyUp(long eventTime, int pressCount) {}
 
         @Override
         public String toString() {
@@ -171,8 +179,8 @@
         }
     }
 
-    static SingleKeyGestureDetector get(Context context) {
-        SingleKeyGestureDetector detector = new SingleKeyGestureDetector();
+    static SingleKeyGestureDetector get(Context context, Looper looper) {
+        SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper);
         sDefaultLongPressTimeout = context.getResources().getInteger(
                 com.android.internal.R.integer.config_globalActionsKeyTimeout);
         sDefaultVeryLongPressTimeout = context.getResources().getInteger(
@@ -180,8 +188,8 @@
         return detector;
     }
 
-    private SingleKeyGestureDetector() {
-        mHandler = new KeyHandler();
+    private SingleKeyGestureDetector(Looper looper) {
+        mHandler = new KeyHandler(looper);
     }
 
     void addRule(SingleKeyRule rule) {
@@ -330,6 +338,13 @@
         }
 
         if (event.getKeyCode() == mActiveRule.mKeyCode) {
+            // key-up action should always be triggered if not processed by long press.
+            Message msgKeyUp =
+                    mHandler.obtainMessage(
+                            MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule);
+            msgKeyUp.setAsynchronous(true);
+            mHandler.sendMessage(msgKeyUp);
+
             // Directly trigger short press when max count is 1.
             if (mActiveRule.getMaxMultiPressCount() == 1) {
                 if (DEBUG) {
@@ -402,8 +417,8 @@
     }
 
     private class KeyHandler extends Handler {
-        KeyHandler() {
-            super(Looper.myLooper());
+        KeyHandler(Looper looper) {
+            super(looper);
         }
 
         @Override
@@ -417,6 +432,12 @@
             final int keyCode = msg.arg1;
             final int pressCount = msg.arg2;
             switch(msg.what) {
+                case MSG_KEY_UP:
+                    if (DEBUG) {
+                        Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode));
+                    }
+                    rule.onKeyUp(mLastDownTime, pressCount);
+                    break;
                 case MSG_KEY_LONG_PRESS:
                     if (DEBUG) {
                         Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 1da9dd7..607d435 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,5 +1,5 @@
 aconfig_declarations {
-    name: "power_optimization_flags",
+    name: "backstage_power_flags",
     package: "com.android.server.power.optimization",
     srcs: [
         "stats/*.aconfig",
@@ -7,6 +7,6 @@
 }
 
 java_aconfig_library {
-    name: "power_optimization_flags_lib",
-    aconfig_declarations: "power_optimization_flags",
+    name: "backstage_power_flags_lib",
+    aconfig_declarations: "backstage_power_flags",
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dfbcbae6..4a4214f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3347,8 +3347,6 @@
             } else {
                 startDreaming = false;
             }
-            Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming
-                    + " wakefulness=" + wakefulnessToString(wakefulness));
         }
 
         // Start dreaming if needed.
@@ -3383,23 +3381,19 @@
             if (startDreaming && isDreaming) {
                 mDreamsBatteryLevelDrain = 0;
                 if (wakefulness == WAKEFULNESS_DOZING) {
-                    Slog.i(TAG, "Dozing powerGroup " + groupId);
+                    Slog.i(TAG, "Dozing...");
                 } else {
-                    Slog.i(TAG, "Dreaming powerGroup " + groupId);
+                    Slog.i(TAG, "Dreaming...");
                 }
             }
 
             // If preconditions changed, wait for the next iteration to determine
             // whether the dream should continue (or be restarted).
             final PowerGroup powerGroup = mPowerGroups.get(groupId);
-            final int newWakefulness = powerGroup.getWakefulnessLocked();
             if (powerGroup.isSandmanSummonedLocked()
-                    || newWakefulness != wakefulness) {
+                    || powerGroup.getWakefulnessLocked() != wakefulness) {
                 return; // wait for next cycle
             }
-            Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming
-                    + " wakefulness=" + newWakefulness);
-
 
             // Determine whether the dream should continue.
             long now = mClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 88c2e09..dd39fb0 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -34,7 +34,7 @@
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.StatsEvent;
 
 import com.android.internal.annotations.GuardedBy;
@@ -256,10 +256,11 @@
 
     @VisibleForTesting
     final class MyUidObserver extends UidObserver {
-        private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
-
+        private final Object mCacheLock = new Object();
+        @GuardedBy("mCacheLock")
+        private final SparseIntArray mProcStatesCache = new SparseIntArray();
         public boolean isUidForeground(int uid) {
-            synchronized (mLock) {
+            synchronized (mCacheLock) {
                 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
                         <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
             }
@@ -268,6 +269,9 @@
         @Override
         public void onUidGone(int uid, boolean disabled) {
             FgThread.getHandler().post(() -> {
+                synchronized (mCacheLock) {
+                    mProcStatesCache.delete(uid);
+                }
                 synchronized (mLock) {
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
@@ -280,7 +284,6 @@
                             sessionSet.valueAt(j).close();
                         }
                     }
-                    mProcStatesCache.delete(uid);
                 }
             });
         }
@@ -292,15 +295,18 @@
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             FgThread.getHandler().post(() -> {
-                synchronized (mLock) {
+                synchronized (mCacheLock) {
                     mProcStatesCache.put(uid, procState);
+                }
+                boolean shouldAllowUpdate = isUidForeground(uid);
+                synchronized (mLock) {
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
                         return;
                     }
                     for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
                         for (AppHintSession s : sessionSet) {
-                            s.onProcStateChanged();
+                            s.onProcStateChanged(shouldAllowUpdate);
                         }
                     }
                 }
@@ -429,10 +435,10 @@
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                 return;
             }
+            pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
+            pw.println("HAL Support: " + isHalSupported());
+            pw.println("Active Sessions:");
             synchronized (mLock) {
-                pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
-                pw.println("HAL Support: " + isHalSupported());
-                pw.println("Active Sessions:");
                 for (int i = 0; i < mActiveSessions.size(); i++) {
                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
@@ -476,7 +482,8 @@
             mHalSessionPtr = halSessionPtr;
             mTargetDurationNanos = durationNanos;
             mUpdateAllowed = true;
-            updateHintAllowed();
+            final boolean allowed = mUidObserver.isUidForeground(mUid);
+            updateHintAllowed(allowed);
             try {
                 token.linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -486,9 +493,8 @@
         }
 
         @VisibleForTesting
-        boolean updateHintAllowed() {
-            synchronized (mLock) {
-                final boolean allowed = mUidObserver.isUidForeground(mUid);
+        boolean updateHintAllowed(boolean allowed) {
+            synchronized (this) {
                 if (allowed && !mUpdateAllowed) resume();
                 if (!allowed && mUpdateAllowed) pause();
                 mUpdateAllowed = allowed;
@@ -498,8 +504,8 @@
 
         @Override
         public void updateTargetWorkDuration(long targetDurationNanos) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(targetDurationNanos > 0, "Expected"
@@ -511,8 +517,8 @@
 
         @Override
         public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
@@ -534,11 +540,13 @@
         /** TODO: consider monitor session threads and close session if any thread is dead. */
         @Override
         public void close() {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
                 mHalSessionPtr = 0;
                 mToken.unlinkToDeath(this, 0);
+            }
+            synchronized (mLock) {
                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
                 if (tokenMap == null) {
                     Slogf.w(TAG, "UID %d is not present in active session map", mUid);
@@ -557,8 +565,8 @@
 
         @Override
         public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
@@ -568,7 +576,7 @@
         }
 
         public void setThreads(@NonNull int[] tids) {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) {
                     return;
                 }
@@ -588,7 +596,7 @@
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
-                if (!updateHintAllowed()) {
+                if (!mUpdateAllowed) {
                     Slogf.v(TAG, "update hint not allowed, storing tids.");
                     mNewThreadIds = tids;
                     return;
@@ -599,13 +607,15 @@
         }
 
         public int[] getThreadIds() {
-            return mThreadIds;
+            synchronized (this) {
+                return Arrays.copyOf(mThreadIds, mThreadIds.length);
+            }
         }
 
         @Override
         public void setMode(int mode, boolean enabled) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
@@ -614,19 +624,19 @@
             }
         }
 
-        private void onProcStateChanged() {
-            updateHintAllowed();
+        private void onProcStateChanged(boolean updateAllowed) {
+            updateHintAllowed(updateAllowed);
         }
 
         private void pause() {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halPauseHintSession(mHalSessionPtr);
             }
         }
 
         private void resume() {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halResumeHintSession(mHalSessionPtr);
                 if (mNewThreadIds != null) {
@@ -638,12 +648,12 @@
         }
 
         private void dump(PrintWriter pw, String prefix) {
-            synchronized (mLock) {
+            synchronized (this) {
                 pw.println(prefix + "SessionPID: " + mPid);
                 pw.println(prefix + "SessionUID: " + mUid);
                 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
                 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
-                pw.println(prefix + "SessionAllowed: " + updateHintAllowed());
+                pw.println(prefix + "SessionAllowed: " + mUpdateAllowed);
             }
         }
 
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
new file mode 100644
index 0000000..10c5362
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "postsubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.power.hint"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index 6cc9d0a..aadd03b 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -18,15 +18,25 @@
 
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
 
 import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
-import java.io.PrintWriter;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -34,25 +44,65 @@
  * etc) covering a specific period of power usage history.
  */
 class AggregatedPowerStats {
+    private static final String TAG = "AggregatedPowerStats";
+    private static final int MAX_CLOCK_UPDATES = 100;
+    private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
+
     private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
 
-    @CurrentTimeMillisLong
-    private long mStartTime;
+    static class ClockUpdate {
+        public long monotonicTime;
+        @CurrentTimeMillisLong public long currentTime;
+    }
+
+    private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
 
     @DurationMillisLong
     private long mDurationMs;
 
-    AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
-        this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+    AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+        List<AggregatedPowerStatsConfig.PowerComponent> configs =
+                aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
+        mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
+        for (int i = 0; i < configs.size(); i++) {
+            mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(configs.get(i));
+        }
     }
 
-    void setStartTime(@CurrentTimeMillisLong long startTime) {
-        mStartTime = startTime;
+    /**
+     * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
+     * there may be multiple clock updates in one set of aggregated stats.
+     *
+     * @param monotonicTime monotonic time in milliseconds, see
+     * {@link com.android.internal.os.MonotonicClock}
+     * @param currentTime   current time in milliseconds, see {@link System#currentTimeMillis()}
+     */
+    void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
+        ClockUpdate clockUpdate = new ClockUpdate();
+        clockUpdate.monotonicTime = monotonicTime;
+        clockUpdate.currentTime = currentTime;
+        if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
+            mClockUpdates.add(clockUpdate);
+        } else {
+            Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
+                        + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
+            mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
+        }
     }
 
-    @CurrentTimeMillisLong
-    public long getStartTime() {
-        return mStartTime;
+    /**
+     * Start time according to {@link com.android.internal.os.MonotonicClock}
+     */
+    long getStartTime() {
+        if (mClockUpdates.isEmpty()) {
+            return 0;
+        } else {
+            return mClockUpdates.get(0).monotonicTime;
+        }
+    }
+
+    List<ClockUpdate> getClockUpdates() {
+        return mClockUpdates;
     }
 
     void setDuration(long durationMs) {
@@ -73,13 +123,14 @@
         return null;
     }
 
-    void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+    void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
+            long time) {
         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
             stats.setState(stateId, state, time);
         }
     }
 
-    void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+    void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
             long time) {
         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
             stats.setUidState(uid, stateId, state, time);
@@ -106,20 +157,90 @@
     }
 
     void reset() {
-        mStartTime = 0;
+        mClockUpdates.clear();
         mDurationMs = 0;
         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
             stats.reset();
         }
     }
 
-    void dump(PrintWriter pw) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
-        ipw.print("Start time: ");
-        ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
-        ipw.print(" duration: ");
-        ipw.print(mDurationMs);
-        ipw.println();
+    public void writeXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+        for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+            stats.writeXml(serializer);
+        }
+        serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+        serializer.flush();
+    }
+
+    @NonNull
+    public static AggregatedPowerStats createFromXml(
+            TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
+            throws XmlPullParserException, IOException {
+        AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
+        boolean inElement = false;
+        boolean skipToEnd = false;
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
+            if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case XML_TAG_AGGREGATED_POWER_STATS:
+                        inElement = true;
+                        break;
+                    case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT:
+                        if (!inElement) {
+                            break;
+                        }
+
+                        int powerComponentId = parser.getAttributeInt(null,
+                                PowerComponentAggregatedPowerStats.XML_ATTR_ID);
+                        for (PowerComponentAggregatedPowerStats powerComponent :
+                                stats.mPowerComponentStats) {
+                            if (powerComponent.powerComponentId == powerComponentId) {
+                                if (!powerComponent.readFromXml(parser)) {
+                                    skipToEnd = true;
+                                }
+                                break;
+                            }
+                        }
+                        break;
+                }
+            }
+            eventType = parser.next();
+        }
+        return stats;
+    }
+
+    void dump(IndentingPrintWriter ipw) {
+        StringBuilder sb = new StringBuilder();
+        long baseTime = 0;
+        for (int i = 0; i < mClockUpdates.size(); i++) {
+            ClockUpdate clockUpdate = mClockUpdates.get(i);
+            sb.setLength(0);
+            if (i == 0) {
+                baseTime = clockUpdate.monotonicTime;
+                sb.append("Start time: ")
+                        .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime))
+                        .append(" (")
+                        .append(baseTime)
+                        .append(") duration: ")
+                        .append(mDurationMs);
+                ipw.println(sb);
+            } else {
+                sb.setLength(0);
+                sb.append("Clock update:  ");
+                TimeUtils.formatDuration(
+                        clockUpdate.monotonicTime - baseTime, sb,
+                        TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
+                sb.append(" ").append(
+                        DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime));
+                ipw.increaseIndent();
+                ipw.println(sb);
+                ipw.decreaseIndent();
+            }
+        }
 
         ipw.println("Device");
         ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
new file mode 100644
index 0000000..43fd15d
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Configuration that controls how power stats are aggregated.  It determines which state changes
+ * are to be considered as essential dimensions ("tracked states") for each power component (CPU,
+ * WiFi, etc).  Also, it determines which states are tracked globally and which ones on a per-UID
+ * basis.
+ */
+public class AggregatedPowerStatsConfig {
+    public static final int STATE_POWER = 0;
+    public static final int STATE_SCREEN = 1;
+    public static final int STATE_PROCESS_STATE = 2;
+
+    @IntDef({
+            STATE_POWER,
+            STATE_SCREEN,
+            STATE_PROCESS_STATE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TrackedState {
+    }
+
+    static final String STATE_NAME_POWER = "pwr";
+    static final int POWER_STATE_BATTERY = 0;
+    static final int POWER_STATE_OTHER = 1;   // Plugged in, or on wireless charger, etc.
+    static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+    static final String STATE_NAME_SCREEN = "scr";
+    static final int SCREEN_STATE_ON = 0;
+    static final int SCREEN_STATE_OTHER = 1;  // Off, doze etc
+    static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+    static final String STATE_NAME_PROCESS_STATE = "ps";
+    static final String[] STATE_LABELS_PROCESS_STATE;
+
+    static {
+        String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+        for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+            procStateLabels[i] = BatteryConsumer.processStateToString(i);
+        }
+        STATE_LABELS_PROCESS_STATE = procStateLabels;
+    }
+
+    /**
+     * Configuration for a give power component (CPU, WiFi, etc)
+     */
+    public static class PowerComponent {
+        private final int mPowerComponentId;
+        private @TrackedState int[] mTrackedDeviceStates;
+        private @TrackedState int[] mTrackedUidStates;
+        private AggregatedPowerStatsProcessor mProcessor = NO_OP_PROCESSOR;
+
+        PowerComponent(int powerComponentId) {
+            this.mPowerComponentId = powerComponentId;
+        }
+
+        /**
+         * Configures which states should be tracked as separate dimensions for the entire device.
+         */
+        public PowerComponent trackDeviceStates(@TrackedState int... states) {
+            mTrackedDeviceStates = states;
+            return this;
+        }
+
+        /**
+         * Configures which states should be tracked as separate dimensions on a per-UID basis.
+         */
+        public PowerComponent trackUidStates(@TrackedState int... states) {
+            mTrackedUidStates = states;
+            return this;
+        }
+
+        /**
+         * Takes an object that should be invoked for every aggregated stats span
+         * before giving the aggregates stats to consumers. The processor can complete the
+         * aggregation process, for example by computing estimated power usage.
+         */
+        public PowerComponent setProcessor(@NonNull AggregatedPowerStatsProcessor processor) {
+            mProcessor = processor;
+            return this;
+        }
+
+        public int getPowerComponentId() {
+            return mPowerComponentId;
+        }
+
+        public MultiStateStats.States[] getDeviceStateConfig() {
+            return new MultiStateStats.States[]{
+                    new MultiStateStats.States(STATE_NAME_POWER,
+                            isTracked(mTrackedDeviceStates, STATE_POWER),
+                            STATE_LABELS_POWER),
+                    new MultiStateStats.States(STATE_NAME_SCREEN,
+                            isTracked(mTrackedDeviceStates, STATE_SCREEN),
+                            STATE_LABELS_SCREEN),
+            };
+        }
+
+        public MultiStateStats.States[] getUidStateConfig() {
+            return new MultiStateStats.States[]{
+                    new MultiStateStats.States(STATE_NAME_POWER,
+                            isTracked(mTrackedUidStates, STATE_POWER),
+                            AggregatedPowerStatsConfig.STATE_LABELS_POWER),
+                    new MultiStateStats.States(STATE_NAME_SCREEN,
+                            isTracked(mTrackedUidStates, STATE_SCREEN),
+                            AggregatedPowerStatsConfig.STATE_LABELS_SCREEN),
+                    new MultiStateStats.States(STATE_NAME_PROCESS_STATE,
+                            isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+                            AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE),
+            };
+        }
+
+        @NonNull
+        public AggregatedPowerStatsProcessor getProcessor() {
+            return mProcessor;
+        }
+
+        private boolean isTracked(int[] trackedStates, int state) {
+            if (trackedStates == null) {
+                return false;
+            }
+
+            for (int trackedState : trackedStates) {
+                if (trackedState == state) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private final List<PowerComponent> mPowerComponents = new ArrayList<>();
+
+    /**
+     * Creates a configuration for the specified power component, which may be one of the
+     * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
+     * a custom power component.
+     */
+    public PowerComponent trackPowerComponent(int powerComponentId) {
+        PowerComponent builder = new PowerComponent(powerComponentId);
+        mPowerComponents.add(builder);
+        return builder;
+    }
+
+    public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
+        return mPowerComponents;
+    }
+
+    private static final AggregatedPowerStatsProcessor NO_OP_PROCESSOR =
+            new AggregatedPowerStatsProcessor() {
+                @Override
+                public void finish(PowerComponentAggregatedPowerStats stats) {
+                }
+
+                @Override
+                public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+                    return Arrays.toString(stats);
+                }
+
+                @Override
+                public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+                    return Arrays.toString(stats);
+                }
+            };
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
new file mode 100644
index 0000000..5fd8ddf
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/*
+ * The power estimation algorithm used by AggregatedPowerStatsProcessor can roughly be
+ * described like this:
+ *
+ * 1. Estimate power usage for each state combination (e.g. power-battery/screen-on) using
+ * a metric such as CPU time-in-state.
+ *
+ * 2. Combine estimates obtain in step 1, aggregating across states that are *not* tracked
+ * per UID.
+ *
+ * 2. For each UID, compute the proportion of the combined estimates in each state
+ * and attribute the corresponding portion of the total power estimate in that state to the UID.
+ */
+abstract class AggregatedPowerStatsProcessor {
+    private static final String TAG = "AggregatedPowerStatsProcessor";
+
+    private static final int INDEX_DOES_NOT_EXIST = -1;
+    private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
+
+    abstract void finish(PowerComponentAggregatedPowerStats stats);
+
+    abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats);
+
+    abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats);
+
+    protected static class PowerEstimationPlan {
+        private final AggregatedPowerStatsConfig.PowerComponent mConfig;
+        public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
+        public List<CombinedDeviceStateEstimate> combinedDeviceStateEstimations = new ArrayList<>();
+        public List<UidStateEstimate> uidStateEstimates = new ArrayList<>();
+
+        public PowerEstimationPlan(AggregatedPowerStatsConfig.PowerComponent config) {
+            mConfig = config;
+            addDeviceStateEstimations();
+            combineDeviceStateEstimations();
+            addUidStateEstimations();
+        }
+
+        private void addDeviceStateEstimations() {
+            MultiStateStats.States[] config = mConfig.getDeviceStateConfig();
+            int[][] deviceStateCombinations = getAllTrackedStateCombinations(config);
+            for (int[] deviceStateCombination : deviceStateCombinations) {
+                deviceStateEstimations.add(
+                        new DeviceStateEstimation(config, deviceStateCombination));
+            }
+        }
+
+        private void combineDeviceStateEstimations() {
+            MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig();
+            MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig();
+            MultiStateStats.States[] deviceStatesTrackedPerUid =
+                    new MultiStateStats.States[deviceStateConfig.length];
+
+            for (int i = 0; i < deviceStateConfig.length; i++) {
+                if (!deviceStateConfig[i].isTracked()) {
+                    continue;
+                }
+
+                int index = findTrackedStateByName(uidStateConfig, deviceStateConfig[i].getName());
+                if (index != INDEX_DOES_NOT_EXIST && uidStateConfig[index].isTracked()) {
+                    deviceStatesTrackedPerUid[i] = deviceStateConfig[i];
+                }
+            }
+
+            combineDeviceStateEstimationsRecursively(deviceStateConfig, deviceStatesTrackedPerUid,
+                    new int[deviceStateConfig.length], 0);
+        }
+
+        private void combineDeviceStateEstimationsRecursively(
+                MultiStateStats.States[] deviceStateConfig,
+                MultiStateStats.States[] deviceStatesTrackedPerUid, int[] stateValues, int state) {
+            if (state >= deviceStateConfig.length) {
+                DeviceStateEstimation dse = getDeviceStateEstimate(stateValues);
+                CombinedDeviceStateEstimate cdse = getCombinedDeviceStateEstimate(
+                        deviceStatesTrackedPerUid, stateValues);
+                if (cdse == null) {
+                    cdse = new CombinedDeviceStateEstimate(deviceStatesTrackedPerUid, stateValues);
+                    combinedDeviceStateEstimations.add(cdse);
+                }
+                cdse.deviceStateEstimations.add(dse);
+                return;
+            }
+
+            if (deviceStateConfig[state].isTracked()) {
+                for (int stateValue = 0;
+                        stateValue < deviceStateConfig[state].getLabels().length;
+                        stateValue++) {
+                    stateValues[state] = stateValue;
+                    combineDeviceStateEstimationsRecursively(deviceStateConfig,
+                            deviceStatesTrackedPerUid, stateValues, state + 1);
+                }
+            } else {
+                combineDeviceStateEstimationsRecursively(deviceStateConfig,
+                        deviceStatesTrackedPerUid, stateValues, state + 1);
+            }
+        }
+
+        private void addUidStateEstimations() {
+            MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig();
+            MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig();
+            MultiStateStats.States[] uidStatesTrackedForDevice =
+                    new MultiStateStats.States[uidStateConfig.length];
+            MultiStateStats.States[] uidStatesNotTrackedForDevice =
+                    new MultiStateStats.States[uidStateConfig.length];
+
+            for (int i = 0; i < uidStateConfig.length; i++) {
+                if (!uidStateConfig[i].isTracked()) {
+                    continue;
+                }
+
+                int index = findTrackedStateByName(deviceStateConfig, uidStateConfig[i].getName());
+                if (index != INDEX_DOES_NOT_EXIST && deviceStateConfig[index].isTracked()) {
+                    uidStatesTrackedForDevice[i] = uidStateConfig[i];
+                } else {
+                    uidStatesNotTrackedForDevice[i] = uidStateConfig[i];
+                }
+            }
+
+            @AggregatedPowerStatsConfig.TrackedState
+            int[][] uidStateCombinations = getAllTrackedStateCombinations(uidStateConfig);
+            for (int[] stateValues : uidStateCombinations) {
+                CombinedDeviceStateEstimate combined =
+                        getCombinedDeviceStateEstimate(uidStatesTrackedForDevice, stateValues);
+                if (combined == null) {
+                    // This is not supposed to be possible
+                    Log.wtf(TAG, "Mismatch in UID and combined device states: "
+                                 + concatLabels(uidStatesTrackedForDevice, stateValues));
+                    continue;
+                }
+                UidStateEstimate uidStateEstimate = getUidStateEstimate(combined);
+                if (uidStateEstimate == null) {
+                    uidStateEstimate = new UidStateEstimate(combined, uidStatesNotTrackedForDevice);
+                    uidStateEstimates.add(uidStateEstimate);
+                }
+                uidStateEstimate.proportionalEstimates.add(
+                        new UidStateProportionalEstimate(stateValues));
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Step 1. Compute device-wide power estimates for state combinations:\n");
+            for (DeviceStateEstimation deviceStateEstimation : deviceStateEstimations) {
+                sb.append("    ").append(deviceStateEstimation.id).append("\n");
+            }
+            sb.append("Step 2. Combine device-wide estimates that are untracked per UID:\n");
+            boolean any = false;
+            for (CombinedDeviceStateEstimate cdse : combinedDeviceStateEstimations) {
+                if (cdse.deviceStateEstimations.size() <= 1) {
+                    continue;
+                }
+                any = true;
+                sb.append("    ").append(cdse.id).append(": ");
+                for (int i = 0; i < cdse.deviceStateEstimations.size(); i++) {
+                    if (i != 0) {
+                        sb.append(" + ");
+                    }
+                    sb.append(cdse.deviceStateEstimations.get(i).id);
+                }
+                sb.append("\n");
+            }
+            if (!any) {
+                sb.append("    N/A\n");
+            }
+            sb.append("Step 3. Proportionally distribute power estimates to UIDs:\n");
+            for (UidStateEstimate uidStateEstimate : uidStateEstimates) {
+                sb.append("    ").append(uidStateEstimate.combinedDeviceStateEstimate.id)
+                        .append("\n        among: ");
+                for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) {
+                    UidStateProportionalEstimate uspe =
+                            uidStateEstimate.proportionalEstimates.get(i);
+                    if (i != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(concatLabels(uidStateEstimate.states, uspe.stateValues));
+                }
+                sb.append("\n");
+            }
+            return sb.toString();
+        }
+
+        @Nullable
+        public DeviceStateEstimation getDeviceStateEstimate(
+                @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+            String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues);
+            for (int i = 0; i < deviceStateEstimations.size(); i++) {
+                DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i);
+                if (deviceStateEstimation.id.equals(label)) {
+                    return deviceStateEstimation;
+                }
+            }
+            return null;
+        }
+
+        public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate(
+                MultiStateStats.States[] deviceStates,
+                @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+            String label = concatLabels(deviceStates, stateValues);
+            for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) {
+                CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i);
+                if (cdse.id.equals(label)) {
+                    return cdse;
+                }
+            }
+            return null;
+        }
+
+        public UidStateEstimate getUidStateEstimate(CombinedDeviceStateEstimate combined) {
+            for (int i = 0; i < uidStateEstimates.size(); i++) {
+                UidStateEstimate uidStateEstimate = uidStateEstimates.get(i);
+                if (uidStateEstimate.combinedDeviceStateEstimate == combined) {
+                    return uidStateEstimate;
+                }
+            }
+            return null;
+        }
+
+        public void resetIntermediates() {
+            for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
+                deviceStateEstimations.get(i).intermediates = null;
+            }
+            for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
+                deviceStateEstimations.get(i).intermediates = null;
+            }
+            for (int i = uidStateEstimates.size() - 1; i >= 0; i--) {
+                UidStateEstimate uidStateEstimate = uidStateEstimates.get(i);
+                List<UidStateProportionalEstimate> proportionalEstimates =
+                        uidStateEstimate.proportionalEstimates;
+                for (int j = proportionalEstimates.size() - 1; j >= 0; j--) {
+                    proportionalEstimates.get(j).intermediates = null;
+                }
+            }
+        }
+    }
+
+    protected static class DeviceStateEstimation {
+        public final String id;
+        @AggregatedPowerStatsConfig.TrackedState
+        public final int[] stateValues;
+        public Object intermediates;
+
+        public DeviceStateEstimation(MultiStateStats.States[] config,
+                @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+            id = concatLabels(config, stateValues);
+            this.stateValues = stateValues;
+        }
+    }
+
+    protected static class CombinedDeviceStateEstimate {
+        public final String id;
+        public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
+        public Object intermediates;
+
+        public CombinedDeviceStateEstimate(MultiStateStats.States[] config,
+                @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+            id = concatLabels(config, stateValues);
+        }
+    }
+
+    protected static class UidStateEstimate {
+        public final MultiStateStats.States[] states;
+        public CombinedDeviceStateEstimate combinedDeviceStateEstimate;
+        public List<UidStateProportionalEstimate> proportionalEstimates = new ArrayList<>();
+
+        public UidStateEstimate(CombinedDeviceStateEstimate combined,
+                MultiStateStats.States[] states) {
+            combinedDeviceStateEstimate = combined;
+            this.states = states;
+        }
+    }
+
+    protected static class UidStateProportionalEstimate {
+        @AggregatedPowerStatsConfig.TrackedState
+        public final int[] stateValues;
+        public Object intermediates;
+
+        protected UidStateProportionalEstimate(
+                @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+            this.stateValues = stateValues;
+        }
+    }
+
+    private static int findTrackedStateByName(MultiStateStats.States[] states, String name) {
+        for (int i = 0; i < states.length; i++) {
+            if (states[i].getName().equals(name)) {
+                return i;
+            }
+        }
+        return INDEX_DOES_NOT_EXIST;
+    }
+
+    @NonNull
+    private static String concatLabels(MultiStateStats.States[] config,
+            @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+        List<String> labels = new ArrayList<>();
+        for (int state = 0; state < config.length; state++) {
+            if (config[state] != null && config[state].isTracked()) {
+                labels.add(config[state].getName()
+                           + "=" + config[state].getLabels()[stateValues[state]]);
+            }
+        }
+        Collections.sort(labels);
+        return labels.toString();
+    }
+
+    @AggregatedPowerStatsConfig.TrackedState
+    private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) {
+        List<int[]> combinations = new ArrayList<>();
+        MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> {
+            combinations.add(Arrays.copyOf(stateValues, stateValues.length));
+        });
+        return combinations.toArray(new int[combinations.size()][0]);
+    }
+
+    public static double uCtoMah(long chargeUC) {
+        return chargeUC * MILLIAMPHOUR_PER_MICROCOULOMB;
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
new file mode 100644
index 0000000..7ba4330
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
+    public static final String TYPE = "aggregated-power-stats";
+
+    private final AggregatedPowerStats mAggregatedPowerStats;
+
+    AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
+        super(TYPE);
+        mAggregatedPowerStats = aggregatedPowerStats;
+    }
+
+    public AggregatedPowerStats getAggregatedPowerStats() {
+        return mAggregatedPowerStats;
+    }
+
+    @Override
+    void write(TypedXmlSerializer serializer) throws IOException {
+        mAggregatedPowerStats.writeXml(serializer);
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter ipw) {
+        mAggregatedPowerStats.dump(ipw);
+    }
+}
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 613f189..a6558e0 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -124,6 +124,7 @@
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.os.LongMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
 import com.android.internal.os.RailStats;
@@ -283,7 +284,6 @@
     private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
     private int[] mCpuPowerBracketMap;
     private final CpuPowerStatsCollector mCpuPowerStatsCollector;
-    private final PowerStatsAggregator mPowerStatsAggregator;
 
     public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
         return mKernelMemoryStats;
@@ -356,6 +356,11 @@
     protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
 
     @NonNull
+    public BatteryStatsHistory getHistory() {
+        return mHistory;
+    }
+
+    @NonNull
     BatteryStatsHistory copyHistory() {
         return mHistory.copy();
     }
@@ -1718,14 +1723,7 @@
         return mMaxLearnedBatteryCapacityUah;
     }
 
-    public BatteryStatsImpl() {
-        this(Clock.SYSTEM_CLOCK);
-    }
-
-    public BatteryStatsImpl(Clock clock) {
-        this(clock, null);
-    }
-
+    @VisibleForTesting
     public BatteryStatsImpl(Clock clock, File historyDirectory) {
         init(clock);
         mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
@@ -1737,18 +1735,19 @@
             mCheckinFile = null;
             mStatsFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+                    new MonotonicClock(0, mClock));
         } else {
             mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
             mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+                    new MonotonicClock(0, mClock));
         }
         mPlatformIdleStateCallback = null;
         mEnergyConsumerRetriever = null;
         mUserInfoProvider = null;
         mCpuPowerStatsCollector = null;
-        mPowerStatsAggregator = null;
     }
 
     private void init(Clock clock) {
@@ -10906,20 +10905,12 @@
         return mTmpCpuTimeInFreq;
     }
 
-    public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir,
+    public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
+            @NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
             @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
             @Nullable EnergyStatsRetriever energyStatsCb,
             @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
             @NonNull CpuScalingPolicies cpuScalingPolicies) {
-        this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider,
-                powerProfile, cpuScalingPolicies);
-    }
-
-    private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
-            @Nullable File systemDir, @NonNull Handler handler,
-            @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb,
-            @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
-            @NonNull CpuScalingPolicies cpuScalingPolicies) {
         init(clock);
 
         mBatteryStatsConfig = config;
@@ -10936,31 +10927,20 @@
             mCheckinFile = null;
             mDailyFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
         } else {
             mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
             mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
             mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
             mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
         }
 
         mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
-                mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
+                () -> mBatteryVoltageMv, mHandler,
+                mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
         mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
 
-        PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
-        builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
-                .trackDeviceStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN)
-                .trackUidStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN,
-                        PowerStatsAggregator.STATE_PROCESS_STATE);
-
-        mPowerStatsAggregator = builder.build();
-
         mStartCount++;
         initTimersAndCounters();
         mOnBattery = mOnBatteryInternal = false;
@@ -14458,6 +14438,7 @@
             final int level, /* not final */ int temp, final int voltageMv, final int chargeUah,
             final int chargeFullUah, final long chargeTimeToFullSeconds,
             final long elapsedRealtimeMs, final long uptimeMs, final long currentTimeMs) {
+
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
@@ -15642,18 +15623,6 @@
         }
     }
 
-    @GuardedBy("this")
-    private void dumpCpuPowerBracketsLocked(PrintWriter pw) {
-        pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):");
-        final int bracketCount = mPowerProfile.getCpuPowerBracketCount();
-        for (int bracket = 0; bracket < bracketCount; bracket++) {
-            pw.print("    ");
-            pw.print(bracket);
-            pw.print(": ");
-            pw.println(mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, bracket));
-        }
-    }
-
     /**
      * Dump EnergyConsumer stats
      */
@@ -15702,20 +15671,23 @@
     }
 
     /**
+     * Schedules an immediate (but asynchronous) collection of PowerStats samples.
+     * Callers will need to wait for the collection to complete on the handler thread.
+     */
+    public void schedulePowerStatsSampleCollection() {
+        if (mCpuPowerStatsCollector == null) {
+            return;
+        }
+        mCpuPowerStatsCollector.forceSchedule();
+    }
+
+    /**
      * Grabs one sample of PowerStats and prints it.
      */
     public void dumpStatsSample(PrintWriter pw) {
         mCpuPowerStatsCollector.collectAndDump(pw);
     }
 
-    /**
-     * Aggregates power stats between the specified times and prints them.
-     */
-    public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
-        mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
-                stats-> stats.dump(pw));
-    }
-
     private final Runnable mWriteAsyncRunnable = () -> {
         synchronized (BatteryStatsImpl.this) {
             writeSyncLocked();
@@ -17007,8 +16979,10 @@
             pw.println();
             dumpConstantsLocked(pw);
 
-            pw.println();
-            dumpCpuPowerBracketsLocked(pw);
+            if (mCpuPowerStatsCollector != null) {
+                pw.println();
+                mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw);
+            }
 
             pw.println();
             dumpEnergyConsumerStatsLocked(pw);
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 f6fa9f2..851a3f7 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -46,7 +46,7 @@
     private static final String TAG = "BatteryUsageStatsProv";
     private final Context mContext;
     private final BatteryStats mStats;
-    private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+    private final PowerStatsStore mPowerStatsStore;
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
     private final Object mLock = new Object();
@@ -58,10 +58,10 @@
 
     @VisibleForTesting
     public BatteryUsageStatsProvider(Context context, BatteryStats stats,
-            BatteryUsageStatsStore batteryUsageStatsStore) {
+            PowerStatsStore powerStatsStore) {
         mContext = context;
         mStats = stats;
-        mBatteryUsageStatsStore = batteryUsageStatsStore;
+        mPowerStatsStore = powerStatsStore;
         mPowerProfile = stats instanceof BatteryStatsImpl
                 ? ((BatteryStatsImpl) stats).getPowerProfile()
                 : new PowerProfile(context);
@@ -314,20 +314,52 @@
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 customEnergyConsumerNames, includePowerModels, includeProcessStateData,
                 minConsumedPowerThreshold);
-        if (mBatteryUsageStatsStore == null) {
-            Log.e(TAG, "BatteryUsageStatsStore is unavailable");
+        if (mPowerStatsStore == null) {
+            Log.e(TAG, "PowerStatsStore is unavailable");
             return builder.build();
         }
 
-        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
-        for (long timestamp : timestamps) {
-            if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) {
-                final BatteryUsageStats snapshot =
-                        mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
-                if (snapshot == null) {
-                    continue;
-                }
+        List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+        for (PowerStatsSpan.Metadata spanMetadata : toc) {
+            if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) {
+                continue;
+            }
 
+            // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the
+            // session end time.
+            //
+            // The following algorithm is correct when there is only one time frame in the span.
+            // When the wall-clock time is adjusted in the middle of an stats span,
+            // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm
+            // only covers some situations, but not others.  When using the resulting data for
+            // analysis, we should always pay attention to the full set of included timeframes.
+            // TODO(b/298459065): switch to monotonic clock
+            long minTime = Long.MAX_VALUE;
+            long maxTime = 0;
+            for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) {
+                long spanEndTime = timeFrame.startTime + timeFrame.duration;
+                minTime = Math.min(minTime, spanEndTime);
+                maxTime = Math.max(maxTime, spanEndTime);
+            }
+
+            // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
+            // while the "to" timestamp is *inclusive*.
+            boolean isInRange =
+                    (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
+                    && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+            if (!isInRange) {
+                continue;
+            }
+
+            PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+                    spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
+            if (powerStatsSpan == null) {
+                continue;
+            }
+
+            for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+                BatteryUsageStats snapshot =
+                        ((BatteryUsageStatsSection) section).getBatteryUsageStats();
                 if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
                         customEnergyConsumerNames)) {
                     Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
new file mode 100644
index 0000000..b95faac
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class BatteryUsageStatsSection extends PowerStatsSpan.Section {
+    public static final String TYPE = "battery-usage-stats";
+
+    private final BatteryUsageStats mBatteryUsageStats;
+
+    BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) {
+        super(TYPE);
+        mBatteryUsageStats = batteryUsageStats;
+    }
+
+    public BatteryUsageStats getBatteryUsageStats() {
+        return mBatteryUsageStats;
+    }
+
+    @Override
+    void write(TypedXmlSerializer serializer) throws IOException {
+        mBatteryUsageStats.writeXml(serializer);
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter ipw) {
+        mBatteryUsageStats.dump(ipw, "");
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
deleted file mode 100644
index 0d7a140..0000000
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.LongArray;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A storage mechanism for BatteryUsageStats snapshots.
- */
-public class BatteryUsageStatsStore {
-    private static final String TAG = "BatteryUsageStatsStore";
-
-    private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of(
-            new BatteryUsageStatsQuery.Builder()
-                    .setMaxStatsAgeMs(0)
-                    .includePowerModels()
-                    .includeProcessStateData()
-                    .build());
-    private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats";
-    private static final String SNAPSHOT_FILE_EXTENSION = ".bus";
-    private static final String DIR_LOCK_FILENAME = ".lock";
-    private static final String CONFIG_FILENAME = "config";
-    private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
-            "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
-    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024;
-
-    private final Context mContext;
-    private final BatteryStatsImpl mBatteryStats;
-    private boolean mSystemReady;
-    private final File mStoreDir;
-    private final File mLockFile;
-    private final ReentrantLock mFileLock = new ReentrantLock();
-    private FileLock mJvmLock;
-    private final AtomicFile mConfigFile;
-    private final long mMaxStorageBytes;
-    private final Handler mHandler;
-    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-
-    public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir,
-            Handler handler) {
-        this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
-    }
-
-    @VisibleForTesting
-    public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir,
-            Handler handler, long maxStorageBytes) {
-        mContext = context;
-        mBatteryStats = batteryStats;
-        mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR);
-        mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
-        mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME));
-        mHandler = handler;
-        mMaxStorageBytes = maxStorageBytes;
-        mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset);
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats);
-    }
-
-    /**
-     * Notifies BatteryUsageStatsStore that the system server is ready.
-     */
-    public void onSystemReady() {
-        mSystemReady = true;
-    }
-
-    private void prepareForBatteryStatsReset(int resetReason) {
-        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) {
-            return;
-        }
-
-        final List<BatteryUsageStats> stats =
-                mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY);
-        if (stats.isEmpty()) {
-            Slog.wtf(TAG, "No battery usage stats generated");
-            return;
-        }
-
-        mHandler.post(() -> storeBatteryUsageStats(stats.get(0)));
-    }
-
-    private void storeBatteryUsageStats(BatteryUsageStats stats) {
-        lockSnapshotDirectory();
-        try {
-            if (!mStoreDir.exists()) {
-                if (!mStoreDir.mkdirs()) {
-                    Slog.e(TAG, "Could not create a directory for battery usage stats snapshots");
-                    return;
-                }
-            }
-            File file = makeSnapshotFilename(stats.getStatsEndTimestamp());
-            try {
-                writeXmlFileLocked(stats, file);
-            } catch (Exception e) {
-                Slog.e(TAG, "Cannot save battery usage stats", e);
-            }
-
-            removeOldSnapshotsLocked();
-        } finally {
-            unlockSnapshotDirectory();
-        }
-    }
-
-    /**
-     * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds
-     * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}.
-     */
-    public long[] listBatteryUsageStatsTimestamps() {
-        LongArray timestamps = new LongArray(100);
-        lockSnapshotDirectory();
-        try {
-            for (File file : mStoreDir.listFiles()) {
-                String fileName = file.getName();
-                if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) {
-                    try {
-                        String fileNameWithoutExtension = fileName.substring(0,
-                                fileName.length() - SNAPSHOT_FILE_EXTENSION.length());
-                        timestamps.add(Long.parseLong(fileNameWithoutExtension));
-                    } catch (NumberFormatException e) {
-                        Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: "
-                                + fileName);
-                    }
-                }
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-        return timestamps.toArray();
-    }
-
-    /**
-     * Reads the specified snapshot of BatteryUsageStats.  Returns null if the snapshot
-     * does not exist.
-     */
-    @Nullable
-    public BatteryUsageStats loadBatteryUsageStats(long timestamp) {
-        lockSnapshotDirectory();
-        try {
-            File file = makeSnapshotFilename(timestamp);
-            try {
-                return readXmlFileLocked(file);
-            } catch (Exception e) {
-                Slog.e(TAG, "Cannot read battery usage stats", e);
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-        return null;
-    }
-
-    /**
-     * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
-     * in persistent file.
-     */
-    public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
-        Properties props = new Properties();
-        lockSnapshotDirectory();
-        try {
-            try (InputStream in = mConfigFile.openRead()) {
-                props.load(in);
-            } catch (IOException e) {
-                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
-            }
-            props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
-                    String.valueOf(timestamp));
-            FileOutputStream out = null;
-            try {
-                out = mConfigFile.startWrite();
-                props.store(out, "Statsd atom pull timestamps");
-                mConfigFile.finishWrite(out);
-            } catch (IOException e) {
-                mConfigFile.failWrite(out);
-                Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-    }
-
-    /**
-     * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
-     * statsd atom pull.
-     */
-    public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
-        Properties props = new Properties();
-        lockSnapshotDirectory();
-        try {
-            try (InputStream in = mConfigFile.openRead()) {
-                props.load(in);
-            } catch (IOException e) {
-                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-        return Long.parseLong(
-                props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
-    }
-
-    private void lockSnapshotDirectory() {
-        mFileLock.lock();
-
-        // Lock the directory from access by other JVMs
-        try {
-            mLockFile.getParentFile().mkdirs();
-            mLockFile.createNewFile();
-            mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
-        } catch (IOException e) {
-            Log.e(TAG, "Cannot lock snapshot directory", e);
-        }
-    }
-
-    private void unlockSnapshotDirectory() {
-        try {
-            mJvmLock.close();
-        } catch (IOException e) {
-            Log.e(TAG, "Cannot unlock snapshot directory", e);
-        } finally {
-            mFileLock.unlock();
-        }
-    }
-
-    /**
-     * Creates a file name by formatting the timestamp as 19-digit zero-padded number.
-     * This ensures that sorted directory list follows the chronological order.
-     */
-    private File makeSnapshotFilename(long statsEndTimestamp) {
-        return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp)
-                + SNAPSHOT_FILE_EXTENSION);
-    }
-
-    private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException {
-        try (OutputStream out = new FileOutputStream(file)) {
-            TypedXmlSerializer serializer = Xml.newBinarySerializer();
-            serializer.setOutput(out, StandardCharsets.UTF_8.name());
-            serializer.startDocument(null, true);
-            stats.writeXml(serializer);
-            serializer.endDocument();
-        }
-    }
-
-    private BatteryUsageStats readXmlFileLocked(File file)
-            throws IOException, XmlPullParserException {
-        try (InputStream in = new FileInputStream(file)) {
-            TypedXmlPullParser parser = Xml.newBinaryPullParser();
-            parser.setInput(in, StandardCharsets.UTF_8.name());
-            return BatteryUsageStats.createFromXml(parser);
-        }
-    }
-
-    private void removeOldSnapshotsLocked() {
-        // Read the directory list into a _sorted_ map.  The alphanumeric ordering
-        // corresponds to the historical order of snapshots because the file names
-        // are timestamps zero-padded to the same length.
-        long totalSize = 0;
-        TreeMap<File, Long> mFileSizes = new TreeMap<>();
-        for (File file : mStoreDir.listFiles()) {
-            final long fileSize = file.length();
-            totalSize += fileSize;
-            if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
-                mFileSizes.put(file, fileSize);
-            }
-        }
-
-        while (totalSize > mMaxStorageBytes) {
-            final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
-            if (entry == null) {
-                break;
-            }
-
-            File file = entry.getKey();
-            if (!file.delete()) {
-                Slog.e(TAG, "Cannot delete battery usage stats " + file);
-            }
-            totalSize -= entry.getValue();
-            mFileSizes.remove(file);
-        }
-    }
-
-    public void removeAllSnapshots() {
-        lockSnapshotDirectory();
-        try {
-            for (File file : mStoreDir.listFiles()) {
-                if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
-                    if (!file.delete()) {
-                        Slog.e(TAG, "Cannot delete battery usage stats " + file);
-                    }
-                }
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
deleted file mode 100644
index 5b3fe06..0000000
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
+++ /dev/null
@@ -1,29 +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.power.stats;
-
-import android.os.BatteryConsumer;
-
-import com.android.internal.os.MultiStateStats;
-
-class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
-
-    CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
-            MultiStateStats.States[] uidStates) {
-        super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
-    }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
new file mode 100644
index 0000000..f40eef2
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.stats;
+
+import android.os.BatteryStats;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProcessor {
+    private static final String TAG = "CpuAggregatedPowerStatsProcessor";
+
+    private static final double HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+    private static final int UNKNOWN = -1;
+
+    private final CpuScalingPolicies mCpuScalingPolicies;
+    // Number of CPU core clusters
+    private final int mCpuClusterCount;
+    // Total number of CPU scaling steps across all clusters
+    private final int mCpuScalingStepCount;
+    // Map of scaling step to the corresponding core cluster mScalingStepToCluster[step]->cluster
+    private final int[] mScalingStepToCluster;
+    // Average power consumed by the CPU when it is powered up (per power_profile.xml)
+    private final double mPowerMultiplierForCpuActive;
+    // Average power consumed by each cluster when it is powered up (per power_profile.xml)
+    private final double[] mPowerMultipliersByCluster;
+    // Average power consumed by each scaling step when running code (per power_profile.xml)
+    private final double[] mPowerMultipliersByScalingStep;
+    // A map used to combine energy consumers into a smaller set, in case power brackets
+    // are defined in a way that does not allow an unambiguous mapping of energy consumers to
+    // brackets
+    private int[] mEnergyConsumerToCombinedEnergyConsumerMap;
+    // A map of combined energy consumers to the corresponding collections of power brackets.
+    // For example, if there are two CPU_CLUSTER rails and each maps to three brackets,
+    // this map will look like this:
+    //     0 : [0, 1, 2]
+    //     1 : [3, 4, 5]
+    private int[][] mCombinedEnergyConsumerToPowerBracketMap;
+
+    // Cached reference to a PowerStats descriptor. Almost never changes in practice,
+    // helping to avoid reparsing the descriptor for every PowerStats span.
+    private PowerStats.Descriptor mLastUsedDescriptor;
+    // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when
+    // mLastUsedDescriptor changes
+    private CpuPowerStatsCollector.StatsArrayLayout mStatsLayout;
+    // Sequence of steps for power estimation and intermediate results.
+    private PowerEstimationPlan mPlan;
+
+    // Temp array for retrieval of device power stats, to avoid repeated allocations
+    private long[] mTmpDeviceStatsArray;
+    // Temp array for retrieval of UID power stats, to avoid repeated allocations
+    private long[] mTmpUidStatsArray;
+
+    public CpuAggregatedPowerStatsProcessor(PowerProfile powerProfile,
+            CpuScalingPolicies scalingPolicies) {
+        mCpuScalingPolicies = scalingPolicies;
+        mCpuScalingStepCount = scalingPolicies.getScalingStepCount();
+        mScalingStepToCluster = new int[mCpuScalingStepCount];
+        mPowerMultipliersByScalingStep = new double[mCpuScalingStepCount];
+
+        int step = 0;
+        int[] policies = scalingPolicies.getPolicies();
+        mCpuClusterCount = policies.length;
+        mPowerMultipliersByCluster = new double[mCpuClusterCount];
+        for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
+            int policy = policies[cluster];
+            mPowerMultipliersByCluster[cluster] =
+                    powerProfile.getAveragePowerForCpuScalingPolicy(policy) / HOUR_IN_MILLIS;
+            int[] frequencies = scalingPolicies.getFrequencies(policy);
+            for (int i = 0; i < frequencies.length; i++) {
+                mScalingStepToCluster[step] = cluster;
+                mPowerMultipliersByScalingStep[step] =
+                        powerProfile.getAveragePowerForCpuScalingStep(policy, i) / HOUR_IN_MILLIS;
+                step++;
+            }
+        }
+        mPowerMultiplierForCpuActive =
+                powerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE) / HOUR_IN_MILLIS;
+    }
+
+    private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) {
+        if (descriptor.equals(mLastUsedDescriptor)) {
+            return;
+        }
+
+        mLastUsedDescriptor = descriptor;
+        mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout();
+        mStatsLayout.fromExtras(descriptor.extras);
+
+        mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
+        mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+    }
+
+    /**
+     * Temporary struct to capture intermediate results of power estimation.
+     */
+    private static final class Intermediates {
+        public long uptime;
+        // Sum of all time-in-step values, combining all time-in-step durations across all cores.
+        public long cumulativeTime;
+        // CPU activity durations per cluster
+        public long[] timeByCluster;
+        // Sums of time-in-step values, aggregated by cluster, combining all cores in the cluster.
+        public long[] cumulativeTimeByCluster;
+        public long[] timeByScalingStep;
+        public double[] powerByCluster;
+        public double[] powerByScalingStep;
+        public long[] powerByEnergyConsumer;
+    }
+
+    /**
+     * Temporary struct to capture intermediate results of power estimation.
+     */
+    private static class DeviceStatsIntermediates {
+        public double power;
+        public long[] timeByBracket;
+        public double[] powerByBracket;
+    }
+
+    @Override
+    public void finish(PowerComponentAggregatedPowerStats stats) {
+        if (stats.getPowerStatsDescriptor() == null) {
+            return;
+        }
+
+        unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor());
+
+        if (mPlan == null) {
+            mPlan = new PowerEstimationPlan(stats.getConfig());
+            if (mStatsLayout.getCpuClusterEnergyConsumerCount() != 0) {
+                initEnergyConsumerToPowerBracketMaps();
+            }
+        }
+
+        Intermediates intermediates = new Intermediates();
+
+        int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
+        if (cpuScalingStepCount != mCpuScalingStepCount) {
+            Log.e(TAG, "Mismatched CPU scaling step count in PowerStats: " + cpuScalingStepCount
+                       + ", expected: " + mCpuScalingStepCount);
+            return;
+        }
+
+        int clusterCount = mStatsLayout.getCpuClusterCount();
+        if (clusterCount != mCpuClusterCount) {
+            Log.e(TAG, "Mismatched CPU cluster count in PowerStats: " + clusterCount
+                       + ", expected: " + mCpuClusterCount);
+            return;
+        }
+
+        computeTotals(stats, intermediates);
+        if (intermediates.cumulativeTime == 0) {
+            return;
+        }
+
+        estimatePowerByScalingStep(intermediates);
+        estimatePowerByDeviceState(stats, intermediates);
+        combineDeviceStateEstimates();
+
+        ArrayList<Integer> uids = new ArrayList<>();
+        stats.collectUids(uids);
+        if (!uids.isEmpty()) {
+            for (int uid : uids) {
+                for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
+                    estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i));
+                }
+            }
+        }
+        mPlan.resetIntermediates();
+    }
+
+    /*
+     * Populate data structures (two maps) needed to use power rail data, aka energy consumers,
+     * to attribute power usage to apps.
+     *
+     * At this point, the algorithm covers only the most basic cases:
+     * - Each cluster is mapped to unique power brackets (possibly multiple for each cluster):
+     *          CL_0: [bracket0, bracket1]
+     *          CL_1: [bracket3]
+     *      In this case, the consumed energy is distributed  to the corresponding brackets
+     *      proportionally.
+     * - Brackets span multiple clusters:
+     *          CL_0: [bracket0, bracket1]
+     *          CL_1: [bracket1, bracket2]
+     *          CL_2: [bracket3, bracket4]
+     *      In this case, we combine energy consumers into groups unambiguously mapped to
+     *      brackets. In the above example, consumed energy for CL_0 and CL_1 will be combined
+     *      because they both map to the same power bracket (bracket1):
+     *          (CL_0+CL_1): [bracket0, bracket1, bracket2]
+     *          CL_2: [bracket3, bracket4]
+     */
+    private void initEnergyConsumerToPowerBracketMaps() {
+        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
+
+        mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount];
+        mCombinedEnergyConsumerToPowerBracketMap = new int[energyConsumerCount][];
+
+        int[] policies = mCpuScalingPolicies.getPolicies();
+        if (energyConsumerCount == policies.length) {
+            int[] scalingStepToPowerBracketMap = mStatsLayout.getScalingStepToPowerBracketMap();
+            ArraySet<Integer>[] clusterToBrackets = new ArraySet[policies.length];
+            int step = 0;
+            for (int cluster = 0; cluster < policies.length; cluster++) {
+                int[] freqs = mCpuScalingPolicies.getFrequencies(policies[cluster]);
+                clusterToBrackets[cluster] = new ArraySet<>(freqs.length);
+                for (int j = 0; j < freqs.length; j++) {
+                    clusterToBrackets[cluster].add(scalingStepToPowerBracketMap[step++]);
+                }
+            }
+
+            ArraySet<Integer>[] combinedEnergyConsumers = new ArraySet[policies.length];
+            int combinedEnergyConsumersCount = 0;
+
+            for (int cluster = 0; cluster < clusterToBrackets.length; cluster++) {
+                int combineWith = UNKNOWN;
+                for (int i = 0; i < combinedEnergyConsumersCount; i++) {
+                    if (containsAny(combinedEnergyConsumers[i], clusterToBrackets[cluster])) {
+                        combineWith = i;
+                        break;
+                    }
+                }
+                if (combineWith != UNKNOWN) {
+                    mEnergyConsumerToCombinedEnergyConsumerMap[cluster] = combineWith;
+                    combinedEnergyConsumers[combineWith].addAll(clusterToBrackets[cluster]);
+                } else {
+                    mEnergyConsumerToCombinedEnergyConsumerMap[cluster] =
+                            combinedEnergyConsumersCount;
+                    combinedEnergyConsumers[combinedEnergyConsumersCount++] =
+                            clusterToBrackets[cluster];
+                }
+            }
+
+            for (int i = combinedEnergyConsumers.length - 1; i >= 0; i--) {
+                mCombinedEnergyConsumerToPowerBracketMap[i] =
+                        new int[combinedEnergyConsumers[i].size()];
+                for (int j = combinedEnergyConsumers[i].size() - 1; j >= 0; j--) {
+                    mCombinedEnergyConsumerToPowerBracketMap[i][j] =
+                            combinedEnergyConsumers[i].valueAt(j);
+                }
+            }
+        } else {
+            // All CPU cluster energy consumers are combined into one, which is
+            // distributed proportionally to all power brackets.
+            int[] map = new int[powerBracketCount];
+            for (int i = 0; i < map.length; i++) {
+                map[i] = i;
+            }
+            mCombinedEnergyConsumerToPowerBracketMap[0] = map;
+        }
+    }
+
+    private static boolean containsAny(ArraySet<Integer> set1, ArraySet<Integer> set2) {
+        for (int i = 0; i < set2.size(); i++) {
+            if (set1.contains(set2.valueAt(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void computeTotals(PowerComponentAggregatedPowerStats stats,
+            Intermediates intermediates) {
+        intermediates.timeByScalingStep = new long[mCpuScalingStepCount];
+        intermediates.timeByCluster = new long[mCpuClusterCount];
+        intermediates.cumulativeTimeByCluster = new long[mCpuClusterCount];
+
+        List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations;
+        for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
+            DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(i);
+            if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues)) {
+                continue;
+            }
+
+            intermediates.uptime += mStatsLayout.getUptime(mTmpDeviceStatsArray);
+
+            for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
+                intermediates.timeByCluster[cluster] +=
+                        mStatsLayout.getTimeByCluster(mTmpDeviceStatsArray, cluster);
+            }
+
+            for (int step = 0; step < mCpuScalingStepCount; step++) {
+                long timeInStep = mStatsLayout.getTimeByScalingStep(mTmpDeviceStatsArray, step);
+                intermediates.cumulativeTime += timeInStep;
+                intermediates.timeByScalingStep[step] += timeInStep;
+                intermediates.cumulativeTimeByCluster[mScalingStepToCluster[step]] += timeInStep;
+            }
+        }
+    }
+
+    private void estimatePowerByScalingStep(Intermediates intermediates) {
+        // CPU consumes some power when it's on - no matter which cores are running.
+        double cpuActivePower = mPowerMultiplierForCpuActive * intermediates.uptime;
+
+        // Additionally, every cluster consumes some power when any of its cores are running
+        intermediates.powerByCluster = new double[mCpuClusterCount];
+        for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
+            intermediates.powerByCluster[cluster] =
+                    mPowerMultipliersByCluster[cluster] * intermediates.timeByCluster[cluster];
+        }
+
+        // Finally, additional power is consumed depending on the frequency scaling
+        intermediates.powerByScalingStep = new double[mCpuScalingStepCount];
+        for (int step = 0; step < mCpuScalingStepCount; step++) {
+            int cluster = mScalingStepToCluster[step];
+
+            double power;
+
+            // Distribute base power proportionally
+            power = cpuActivePower * intermediates.timeByScalingStep[step]
+                    / intermediates.cumulativeTime;
+
+            // Distribute per-cluster power proportionally
+            long cumulativeTimeInCluster = intermediates.cumulativeTimeByCluster[cluster];
+            if (cumulativeTimeInCluster != 0) {
+                power += intermediates.powerByCluster[cluster]
+                         * intermediates.timeByScalingStep[step]
+                         / cumulativeTimeInCluster;
+            }
+
+            power += mPowerMultipliersByScalingStep[step] * intermediates.timeByScalingStep[step];
+
+            intermediates.powerByScalingStep[step] = power;
+        }
+    }
+
+    private void estimatePowerByDeviceState(PowerComponentAggregatedPowerStats stats,
+            Intermediates intermediates) {
+        int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
+        int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
+        int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap();
+        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations;
+        for (int dse = deviceStateEstimations.size() - 1; dse >= 0; dse--) {
+            DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(dse);
+            deviceStateEstimation.intermediates = new DeviceStatsIntermediates();
+            DeviceStatsIntermediates deviceStatsIntermediates =
+                    (DeviceStatsIntermediates) deviceStateEstimation.intermediates;
+            deviceStatsIntermediates.timeByBracket = new long[powerBracketCount];
+            deviceStatsIntermediates.powerByBracket = new double[powerBracketCount];
+
+            stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues);
+            for (int step = 0; step < cpuScalingStepCount; step++) {
+                if (intermediates.timeByScalingStep[step] == 0) {
+                    continue;
+                }
+
+                long timeInStep = mStatsLayout.getTimeByScalingStep(mTmpDeviceStatsArray, step);
+                double stepPower = intermediates.powerByScalingStep[step] * timeInStep
+                                   / intermediates.timeByScalingStep[step];
+
+                int bracket = scalingStepToBracketMap[step];
+                deviceStatsIntermediates.timeByBracket[bracket] += timeInStep;
+                deviceStatsIntermediates.powerByBracket[bracket] += stepPower;
+            }
+
+            if (energyConsumerCount != 0) {
+                adjustEstimatesUsingEnergyConsumers(intermediates, deviceStatsIntermediates);
+            }
+
+            double power = 0;
+            for (int i = deviceStatsIntermediates.powerByBracket.length - 1; i >= 0; i--) {
+                power += deviceStatsIntermediates.powerByBracket[i];
+            }
+            deviceStatsIntermediates.power = power;
+            mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+            stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray);
+        }
+    }
+
+    private void adjustEstimatesUsingEnergyConsumers(
+            Intermediates intermediates, DeviceStatsIntermediates deviceStatsIntermediates) {
+        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        if (energyConsumerCount == 0) {
+            return;
+        }
+
+        if (intermediates.powerByEnergyConsumer == null) {
+            intermediates.powerByEnergyConsumer = new long[energyConsumerCount];
+        } else {
+            Arrays.fill(intermediates.powerByEnergyConsumer, 0);
+        }
+        for (int i = 0; i < energyConsumerCount; i++) {
+            intermediates.powerByEnergyConsumer[mEnergyConsumerToCombinedEnergyConsumerMap[i]] +=
+                    mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i);
+        }
+
+        for (int combinedConsumer = mCombinedEnergyConsumerToPowerBracketMap.length - 1;
+                combinedConsumer >= 0; combinedConsumer--) {
+            int[] combinedEnergyConsumerToPowerBracketMap =
+                    mCombinedEnergyConsumerToPowerBracketMap[combinedConsumer];
+            if (combinedEnergyConsumerToPowerBracketMap == null) {
+                continue;
+            }
+
+            double consumedEnergy = uCtoMah(intermediates.powerByEnergyConsumer[combinedConsumer]);
+
+            double totalModeledPower = 0;
+            for (int bracket : combinedEnergyConsumerToPowerBracketMap) {
+                totalModeledPower += deviceStatsIntermediates.powerByBracket[bracket];
+            }
+            if (totalModeledPower == 0) {
+                continue;
+            }
+
+            for (int bracket : combinedEnergyConsumerToPowerBracketMap) {
+                deviceStatsIntermediates.powerByBracket[bracket] =
+                        consumedEnergy * deviceStatsIntermediates.powerByBracket[bracket]
+                        / totalModeledPower;
+            }
+        }
+    }
+
+    private void combineDeviceStateEstimates() {
+        for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+            CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i);
+            DeviceStatsIntermediates cdseIntermediates = new DeviceStatsIntermediates();
+            cdse.intermediates = cdseIntermediates;
+            int bracketCount = mStatsLayout.getCpuPowerBracketCount();
+            cdseIntermediates.timeByBracket = new long[bracketCount];
+            cdseIntermediates.powerByBracket = new double[bracketCount];
+            List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations;
+            for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) {
+                DeviceStateEstimation dse = deviceStateEstimations.get(j);
+                DeviceStatsIntermediates intermediates =
+                        (DeviceStatsIntermediates) dse.intermediates;
+                cdseIntermediates.power += intermediates.power;
+                for (int k = 0; k < bracketCount; k++) {
+                    cdseIntermediates.timeByBracket[k] += intermediates.timeByBracket[k];
+                    cdseIntermediates.powerByBracket[k] += intermediates.powerByBracket[k];
+                }
+            }
+        }
+    }
+
+    private void estimateUidPowerConsumption(PowerComponentAggregatedPowerStats stats, int uid,
+            UidStateEstimate uidStateEstimate) {
+        CombinedDeviceStateEstimate combinedDeviceStateEstimate =
+                uidStateEstimate.combinedDeviceStateEstimate;
+        DeviceStatsIntermediates cdsIntermediates =
+                (DeviceStatsIntermediates) combinedDeviceStateEstimate.intermediates;
+        for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) {
+            UidStateProportionalEstimate proportionalEstimate =
+                    uidStateEstimate.proportionalEstimates.get(i);
+            if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) {
+                continue;
+            }
+
+            double power = 0;
+            for (int bracket = 0; bracket < mStatsLayout.getCpuPowerBracketCount(); bracket++) {
+                if (cdsIntermediates.timeByBracket[bracket] == 0) {
+                    continue;
+                }
+
+                long timeInBracket = mStatsLayout.getUidTimeByPowerBracket(mTmpUidStatsArray,
+                        bracket);
+                if (timeInBracket == 0) {
+                    continue;
+                }
+
+                power += cdsIntermediates.powerByBracket[bracket] * timeInBracket
+                            / cdsIntermediates.timeByBracket[bracket];
+            }
+
+            mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+            stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+        }
+    }
+
+    @Override
+    public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+        unpackPowerStatsDescriptor(descriptor);
+        StringBuilder sb = new StringBuilder();
+        int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
+        sb.append("steps: [");
+        for (int step = 0; step < cpuScalingStepCount; step++) {
+            if (step != 0) {
+                sb.append(", ");
+            }
+            sb.append(mStatsLayout.getTimeByScalingStep(stats, step));
+        }
+        int clusterCount = mStatsLayout.getCpuClusterCount();
+        sb.append("] clusters: [");
+        for (int cluster = 0; cluster < clusterCount; cluster++) {
+            if (cluster != 0) {
+                sb.append(", ");
+            }
+            sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
+        }
+        sb.append("] uptime: ").append(mStatsLayout.getUptime(stats));
+        int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+        if (energyConsumerCount > 0) {
+            sb.append(" energy: [");
+            for (int i = 0; i < energyConsumerCount; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                sb.append(mStatsLayout.getConsumedEnergy(stats, i));
+            }
+            sb.append("]");
+        }
+        sb.append(" power: ").append(
+                BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats)));
+        return sb.toString();
+    }
+
+    @Override
+    public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+        unpackPowerStatsDescriptor(descriptor);
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
+        for (int bracket = 0; bracket < powerBracketCount; bracket++) {
+            if (bracket != 0) {
+                sb.append(", ");
+            }
+            sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket));
+        }
+        sb.append("] power: ").append(
+                BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats)));
+        return sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 376ca89..a388932 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -16,19 +16,39 @@
 
 package com.android.server.power.stats;
 
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.Handler;
 import android.os.PersistableBundle;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.Keep;
 import com.android.internal.annotations.VisibleForNative;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.LocalServices;
 import com.android.server.power.optimization.Flags;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
 /**
  * Collects snapshots of power-related system statistics.
  * <p>
@@ -36,45 +56,350 @@
  * constructor. Thus the object is not thread-safe except where noted.
  */
 public class CpuPowerStatsCollector extends PowerStatsCollector {
+    private static final String TAG = "CpuPowerStatsCollector";
     private static final long NANOS_PER_MILLIS = 1000000;
+    private static final long ENERGY_UNSPECIFIED = -1;
+    private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
+    private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
+    private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
 
+    private boolean mIsInitialized;
+    private final CpuScalingPolicies mCpuScalingPolicies;
+    private final PowerProfile mPowerProfile;
     private final KernelCpuStatsReader mKernelCpuStatsReader;
-    private final int[] mScalingStepToPowerBracketMap;
-    private final long[] mTempUidStats;
+    private final Supplier<PowerStatsInternal> mPowerStatsSupplier;
+    private final IntSupplier mVoltageSupplier;
+    private final int mDefaultCpuPowerBrackets;
+    private final int mDefaultCpuPowerBracketsPerEnergyConsumer;
+    private long[] mCpuTimeByScalingStep;
+    private long[] mTempCpuTimeByScalingStep;
+    private long[] mTempUidStats;
     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
-    private final int mUidStatsSize;
+    private boolean mIsPerUidTimeInStateSupported;
+    private PowerStatsInternal mPowerStatsInternal;
+    private int[] mCpuEnergyConsumerIds;
+    private PowerStats.Descriptor mPowerStatsDescriptor;
     // Reusable instance
-    private final PowerStats mCpuPowerStats;
+    private PowerStats mCpuPowerStats;
+    private StatsArrayLayout mLayout;
     private long mLastUpdateTimestampNanos;
+    private long mLastUpdateUptimeMillis;
+    private int mLastVoltageMv;
+    private long[] mLastConsumedEnergyUws;
+
+    /**
+     * Captures the positions and lengths of sections of the stats array, such as time-in-state,
+     * power usage estimates etc.
+     */
+    public static class StatsArrayLayout {
+        private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt";
+        private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc";
+        private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc";
+        private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc";
+        private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
+        private static final String EXTRA_DEVICE_UPTIME_POSITION = "du";
+        private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
+        private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
+        private static final String EXTRA_UID_BRACKETS_POSITION = "ub";
+        private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us";
+        private static final String EXTRA_UID_POWER_POSITION = "up";
+
+        private static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+
+        private int mDeviceStatsArrayLength;
+        private int mUidStatsArrayLength;
+
+        private int mDeviceCpuTimeByScalingStepPosition;
+        private int mDeviceCpuTimeByScalingStepCount;
+        private int mDeviceCpuTimeByClusterPosition;
+        private int mDeviceCpuTimeByClusterCount;
+        private int mDeviceCpuUptimePosition;
+        private int mDeviceEnergyConsumerPosition;
+        private int mDeviceEnergyConsumerCount;
+        private int mDevicePowerEstimatePosition;
+
+        private int mUidPowerBracketsPosition;
+        private int mUidPowerBracketCount;
+        private int[][] mEnergyConsumerToPowerBucketMaps;
+        private int mUidPowerEstimatePosition;
+
+        private int[] mScalingStepToPowerBracketMap;
+
+        public int getDeviceStatsArrayLength() {
+            return mDeviceStatsArrayLength;
+        }
+
+        public int getUidStatsArrayLength() {
+            return mUidStatsArrayLength;
+        }
+
+        /**
+         * Declare that the stats array has a section capturing CPU time per scaling step
+         */
+        public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
+            mDeviceCpuTimeByScalingStepPosition = mDeviceStatsArrayLength;
+            mDeviceCpuTimeByScalingStepCount = scalingStepCount;
+            mDeviceStatsArrayLength += scalingStepCount;
+        }
+
+        public int getCpuScalingStepCount() {
+            return mDeviceCpuTimeByScalingStepCount;
+        }
+
+        /**
+         * Saves the time duration in the <code>stats</code> element
+         * corresponding to the CPU scaling <code>state</code>.
+         */
+        public void setTimeByScalingStep(long[] stats, int step, long value) {
+            stats[mDeviceCpuTimeByScalingStepPosition + step] = value;
+        }
+
+        /**
+         * Extracts the time duration from the <code>stats</code> element
+         * corresponding to the CPU scaling <code>step</code>.
+         */
+        public long getTimeByScalingStep(long[] stats, int step) {
+            return stats[mDeviceCpuTimeByScalingStepPosition + step];
+        }
+
+        /**
+         * Declare that the stats array has a section capturing CPU time in each cluster
+         */
+        public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
+            mDeviceCpuTimeByClusterCount = clusterCount;
+            mDeviceCpuTimeByClusterPosition = mDeviceStatsArrayLength;
+            mDeviceStatsArrayLength += clusterCount;
+        }
+
+        public int getCpuClusterCount() {
+            return mDeviceCpuTimeByClusterCount;
+        }
+
+        /**
+         * Saves the time duration in the <code>stats</code> element
+         * corresponding to the CPU <code>cluster</code>.
+         */
+        public void setTimeByCluster(long[] stats, int cluster, long value) {
+            stats[mDeviceCpuTimeByClusterPosition + cluster] = value;
+        }
+
+        /**
+         * Extracts the time duration from the <code>stats</code> element
+         * corresponding to the CPU <code>cluster</code>.
+         */
+        public long getTimeByCluster(long[] stats, int cluster) {
+            return stats[mDeviceCpuTimeByClusterPosition + cluster];
+        }
+
+        /**
+         * Declare that the stats array has a section capturing CPU uptime
+         */
+        public void addDeviceSectionUptime() {
+            mDeviceCpuUptimePosition = mDeviceStatsArrayLength++;
+        }
+
+        /**
+         * Saves the CPU uptime duration in the corresponding <code>stats</code> element.
+         */
+        public void setUptime(long[] stats, long value) {
+            stats[mDeviceCpuUptimePosition] = value;
+        }
+
+        /**
+         * Extracts the CPU uptime duration from the corresponding <code>stats</code> element.
+         */
+        public long getUptime(long[] stats) {
+            return stats[mDeviceCpuUptimePosition];
+        }
+
+        /**
+         * Declares that the stats array has a section capturing EnergyConsumer data from
+         * PowerStatsService.
+         */
+        public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
+            mDeviceEnergyConsumerPosition = mDeviceStatsArrayLength;
+            mDeviceEnergyConsumerCount = energyConsumerCount;
+            mDeviceStatsArrayLength += energyConsumerCount;
+        }
+
+        public int getCpuClusterEnergyConsumerCount() {
+            return mDeviceEnergyConsumerCount;
+        }
+
+        /**
+         * Saves the accumulated energy for the specified rail the corresponding
+         * <code>stats</code> element.
+         */
+        public void setConsumedEnergy(long[] stats, int index, long energy) {
+            stats[mDeviceEnergyConsumerPosition + index] = energy;
+        }
+
+        /**
+         * Extracts the EnergyConsumer data from a device stats array for the specified
+         * EnergyConsumer.
+         */
+        public long getConsumedEnergy(long[] stats, int index) {
+            return stats[mDeviceEnergyConsumerPosition + index];
+        }
+
+        /**
+         * Declare that the stats array has a section capturing a power estimate
+         */
+        public void addDeviceSectionPowerEstimate() {
+            mDevicePowerEstimatePosition = mDeviceStatsArrayLength++;
+        }
+
+        /**
+         * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+         * element of <code>stats</code>.
+         */
+        public void setDevicePowerEstimate(long[] stats, double power) {
+            stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+        }
+
+        /**
+         * Extracts the power estimate from a device stats array and converts it to mAh.
+         */
+        public double getDevicePowerEstimate(long[] stats) {
+            return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+        }
+
+        /**
+         * Declare that the UID stats array has a section capturing CPU time per power bracket.
+         */
+        public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
+            mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
+            mUidPowerBracketsPosition = mUidStatsArrayLength;
+            updatePowerBracketCount();
+            mUidStatsArrayLength += mUidPowerBracketCount;
+        }
+
+        private void updatePowerBracketCount() {
+            mUidPowerBracketCount = 1;
+            for (int bracket : mScalingStepToPowerBracketMap) {
+                if (bracket >= mUidPowerBracketCount) {
+                    mUidPowerBracketCount = bracket + 1;
+                }
+            }
+        }
+
+        public int[] getScalingStepToPowerBracketMap() {
+            return mScalingStepToPowerBracketMap;
+        }
+
+        public int getCpuPowerBracketCount() {
+            return mUidPowerBracketCount;
+        }
+
+        /**
+         * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>.
+         */
+        public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) {
+            stats[mUidPowerBracketsPosition + bracket] = value;
+        }
+
+        /**
+         * Extracts the time in <code>bracket</code> from a UID stats array.
+         */
+        public long getUidTimeByPowerBracket(long[] stats, int bracket) {
+            return stats[mUidPowerBracketsPosition + bracket];
+        }
+
+        /**
+         * Declare that the UID stats array has a section capturing a power estimate
+         */
+        public void addUidSectionPowerEstimate() {
+            mUidPowerEstimatePosition = mUidStatsArrayLength++;
+        }
+
+        /**
+         * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+         * element of <code>stats</code>.
+         */
+        public void setUidPowerEstimate(long[] stats, double power) {
+            stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+        }
+
+        /**
+         * Extracts the power estimate from a UID stats array and converts it to mAh.
+         */
+        public double getUidPowerEstimate(long[] stats) {
+            return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+        }
+
+        /**
+         * Copies the elements of the stats array layout into <code>extras</code>
+         */
+        public void toExtras(PersistableBundle extras) {
+            extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
+                    mDeviceCpuTimeByScalingStepPosition);
+            extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
+                    mDeviceCpuTimeByScalingStepCount);
+            extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION,
+                    mDeviceCpuTimeByClusterPosition);
+            extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
+                    mDeviceCpuTimeByClusterCount);
+            extras.putInt(EXTRA_DEVICE_UPTIME_POSITION, mDeviceCpuUptimePosition);
+            extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
+                    mDeviceEnergyConsumerPosition);
+            extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
+                    mDeviceEnergyConsumerCount);
+            extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
+            extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
+            extras.putIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
+                    mScalingStepToPowerBracketMap);
+            extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+        }
+
+        /**
+         * Retrieves elements of the stats array layout from <code>extras</code>
+         */
+        public void fromExtras(PersistableBundle extras) {
+            mDeviceCpuTimeByScalingStepPosition =
+                    extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
+            mDeviceCpuTimeByScalingStepCount =
+                    extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT);
+            mDeviceCpuTimeByClusterPosition =
+                    extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
+            mDeviceCpuTimeByClusterCount =
+                    extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
+            mDeviceCpuUptimePosition = extras.getInt(EXTRA_DEVICE_UPTIME_POSITION);
+            mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
+            mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
+            mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
+            mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
+            mScalingStepToPowerBracketMap =
+                    extras.getIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
+            if (mScalingStepToPowerBracketMap == null) {
+                mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
+            }
+            updatePowerBracketCount();
+            mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
+        }
+    }
 
     public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
-                                  Handler handler, long throttlePeriodMs) {
+            IntSupplier voltageSupplier, Handler handler, long throttlePeriodMs) {
         this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(),
-                throttlePeriodMs, Clock.SYSTEM_CLOCK);
+                () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier,
+                throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS,
+                DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER);
     }
 
     public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
                                   Handler handler, KernelCpuStatsReader kernelCpuStatsReader,
-                                  long throttlePeriodMs, Clock clock) {
+            Supplier<PowerStatsInternal> powerStatsSupplier,
+            IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock,
+            int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) {
         super(handler, throttlePeriodMs, clock);
+        mCpuScalingPolicies = cpuScalingPolicies;
+        mPowerProfile = powerProfile;
         mKernelCpuStatsReader = kernelCpuStatsReader;
+        mPowerStatsSupplier = powerStatsSupplier;
+        mVoltageSupplier = voltageSupplier;
+        mDefaultCpuPowerBrackets = defaultCpuPowerBrackets;
+        mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer;
 
-        int scalingStepCount = cpuScalingPolicies.getScalingStepCount();
-        mScalingStepToPowerBracketMap = new int[scalingStepCount];
-        int index = 0;
-        for (int policy : cpuScalingPolicies.getPolicies()) {
-            int[] frequencies = cpuScalingPolicies.getFrequencies(policy);
-            for (int step = 0; step < frequencies.length; step++) {
-                int bracket = powerProfile.getCpuPowerBracketForScalingStep(policy, step);
-                mScalingStepToPowerBracketMap[index++] = bracket;
-            }
-        }
-        mUidStatsSize = powerProfile.getCpuPowerBracketCount();
-        mTempUidStats = new long[mUidStatsSize];
-
-        mCpuPowerStats = new PowerStats(
-                new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize,
-                        new PersistableBundle()));
     }
 
     /**
@@ -84,43 +409,355 @@
         setEnabled(Flags.streamlinedBatteryStats());
     }
 
+    private void ensureInitialized() {
+        if (mIsInitialized) {
+            return;
+        }
+
+        if (!isEnabled()) {
+            return;
+        }
+
+        mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature();
+        mPowerStatsInternal = mPowerStatsSupplier.get();
+
+        if (mPowerStatsInternal != null) {
+            readCpuEnergyConsumerIds();
+        } else {
+            mCpuEnergyConsumerIds = new int[0];
+        }
+
+        int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount();
+        mCpuTimeByScalingStep = new long[cpuScalingStepCount];
+        mTempCpuTimeByScalingStep = new long[cpuScalingStepCount];
+        int[] scalingStepToPowerBracketMap = initPowerBrackets();
+
+        mLayout = new StatsArrayLayout();
+        mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount);
+        mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length);
+        mLayout.addDeviceSectionUptime();
+        mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length);
+        mLayout.addDeviceSectionPowerEstimate();
+        mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap);
+        mLayout.addUidSectionPowerEstimate();
+
+        PersistableBundle extras = new PersistableBundle();
+        mLayout.toExtras(extras);
+
+        mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
+                mLayout.getDeviceStatsArrayLength(), mLayout.getUidStatsArrayLength(), extras);
+        mCpuPowerStats = new PowerStats(mPowerStatsDescriptor);
+
+        mTempUidStats = new long[mLayout.getCpuPowerBracketCount()];
+
+        mIsInitialized = true;
+    }
+
+    private void readCpuEnergyConsumerIds() {
+        EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
+        if (energyConsumerInfo == null) {
+            mCpuEnergyConsumerIds = new int[0];
+            return;
+        }
+
+        List<EnergyConsumer> cpuEnergyConsumers = new ArrayList<>();
+        for (EnergyConsumer energyConsumer : energyConsumerInfo) {
+            if (energyConsumer.type == EnergyConsumerType.CPU_CLUSTER) {
+                cpuEnergyConsumers.add(energyConsumer);
+            }
+        }
+        if (cpuEnergyConsumers.isEmpty()) {
+            return;
+        }
+
+        cpuEnergyConsumers.sort(Comparator.comparing(c -> c.ordinal));
+
+        mCpuEnergyConsumerIds = new int[cpuEnergyConsumers.size()];
+        for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) {
+            mCpuEnergyConsumerIds[i] = cpuEnergyConsumers.get(i).id;
+        }
+        mLastConsumedEnergyUws = new long[cpuEnergyConsumers.size()];
+        Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+    }
+
+    private int[] initPowerBrackets() {
+        if (mPowerProfile.getCpuPowerBracketCount() != PowerProfile.POWER_BRACKETS_UNSPECIFIED) {
+            return initPowerBracketsFromPowerProfile();
+        } else if (mCpuEnergyConsumerIds.length == 0 || mCpuEnergyConsumerIds.length == 1) {
+            return initDefaultPowerBrackets(mDefaultCpuPowerBrackets);
+        } else if (mCpuScalingPolicies.getPolicies().length == mCpuEnergyConsumerIds.length) {
+            return initPowerBracketsByCluster(mDefaultCpuPowerBracketsPerEnergyConsumer);
+        } else {
+            Slog.i(TAG, "Assigning a single power brackets to each CPU_CLUSTER energy consumer."
+                        + " Number of CPU clusters ("
+                        + mCpuScalingPolicies.getPolicies().length
+                        + ") does not match the number of energy consumers ("
+                        + mCpuEnergyConsumerIds.length + "). "
+                        + " Using default power bucket assignment.");
+            return initDefaultPowerBrackets(mDefaultCpuPowerBrackets);
+        }
+    }
+
+    private int[] initPowerBracketsFromPowerProfile() {
+        int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
+        int index = 0;
+        for (int policy : mCpuScalingPolicies.getPolicies()) {
+            int[] frequencies = mCpuScalingPolicies.getFrequencies(policy);
+            for (int step = 0; step < frequencies.length; step++) {
+                int bracket = mPowerProfile.getCpuPowerBracketForScalingStep(policy, step);
+                stepToBracketMap[index++] = bracket;
+            }
+        }
+        return stepToBracketMap;
+    }
+
+    private int[] initPowerBracketsByCluster(int defaultBracketCountPerCluster) {
+        int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
+        int index = 0;
+        int bracketBase = 0;
+        int[] policies = mCpuScalingPolicies.getPolicies();
+        for (int policy : policies) {
+            int[] frequencies = mCpuScalingPolicies.getFrequencies(policy);
+            double[] powerByStep = new double[frequencies.length];
+            for (int step = 0; step < frequencies.length; step++) {
+                powerByStep[step] = mPowerProfile.getAveragePowerForCpuScalingStep(policy, step);
+            }
+
+            int[] policyStepToBracketMap = new int[frequencies.length];
+            mapScalingStepsToDefaultBrackets(policyStepToBracketMap, powerByStep,
+                    defaultBracketCountPerCluster);
+            int maxBracket = 0;
+            for (int step = 0; step < frequencies.length; step++) {
+                int bracket = bracketBase + policyStepToBracketMap[step];
+                stepToBracketMap[index++] = bracket;
+                if (bracket > maxBracket) {
+                    maxBracket = bracket;
+                }
+            }
+            bracketBase = maxBracket + 1;
+        }
+        return stepToBracketMap;
+    }
+
+    private int[] initDefaultPowerBrackets(int defaultCpuPowerBracketCount) {
+        int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
+        double[] powerByStep = new double[mCpuScalingPolicies.getScalingStepCount()];
+        int index = 0;
+        int[] policies = mCpuScalingPolicies.getPolicies();
+        for (int policy : policies) {
+            int[] frequencies = mCpuScalingPolicies.getFrequencies(policy);
+            for (int step = 0; step < frequencies.length; step++) {
+                powerByStep[index++] = mPowerProfile.getAveragePowerForCpuScalingStep(policy, step);
+            }
+        }
+        mapScalingStepsToDefaultBrackets(stepToBracketMap, powerByStep,
+                defaultCpuPowerBracketCount);
+        return stepToBracketMap;
+    }
+
+    private static void mapScalingStepsToDefaultBrackets(int[] stepToBracketMap,
+            double[] powerByStep, int defaultCpuPowerBracketCount) {
+        double minPower = Double.MAX_VALUE;
+        double maxPower = Double.MIN_VALUE;
+        for (final double power : powerByStep) {
+            if (power < minPower) {
+                minPower = power;
+            }
+            if (power > maxPower) {
+                maxPower = power;
+            }
+        }
+        if (powerByStep.length <= defaultCpuPowerBracketCount) {
+            for (int index = 0; index < stepToBracketMap.length; index++) {
+                stepToBracketMap[index] = index;
+            }
+        } else {
+            final double minLogPower = Math.log(minPower);
+            final double logBracket = (Math.log(maxPower) - minLogPower)
+                                      / defaultCpuPowerBracketCount;
+
+            for (int step = 0; step < powerByStep.length; step++) {
+                int bracket = (int) ((Math.log(powerByStep[step]) - minLogPower) / logBracket);
+                if (bracket >= defaultCpuPowerBracketCount) {
+                    bracket = defaultCpuPowerBracketCount - 1;
+                }
+                stepToBracketMap[step] = bracket;
+            }
+        }
+    }
+
+    /**
+     * Prints the definitions of power brackets.
+     */
+    public void dumpCpuPowerBracketsLocked(PrintWriter pw) {
+        ensureInitialized();
+
+        pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):");
+        for (int bracket = 0; bracket < mLayout.getCpuPowerBracketCount(); bracket++) {
+            pw.print("    ");
+            pw.print(bracket);
+            pw.print(": ");
+            pw.println(getCpuPowerBracketDescription(bracket));
+        }
+    }
+
+    /**
+     * Description of a CPU power bracket: which cluster/frequency combinations are included.
+     */
+    @VisibleForTesting
+    public String getCpuPowerBracketDescription(int powerBracket) {
+        ensureInitialized();
+
+        int[] stepToPowerBracketMap = mLayout.getScalingStepToPowerBracketMap();
+        StringBuilder sb = new StringBuilder();
+        int index = 0;
+        int[] policies = mCpuScalingPolicies.getPolicies();
+        for (int policy : policies) {
+            int[] freqs = mCpuScalingPolicies.getFrequencies(policy);
+            for (int step = 0; step < freqs.length; step++) {
+                if (stepToPowerBracketMap[index] != powerBracket) {
+                    index++;
+                    continue;
+                }
+
+                if (sb.length() != 0) {
+                    sb.append(", ");
+                }
+                if (policies.length > 1) {
+                    sb.append(policy).append('/');
+                }
+                sb.append(freqs[step] / 1000);
+                sb.append('(');
+                sb.append(String.format(Locale.US, "%.1f",
+                        mPowerProfile.getAveragePowerForCpuScalingStep(policy, step)));
+                sb.append(')');
+
+                index++;
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns the descriptor of PowerStats produced by this collector.
+     */
+    @VisibleForTesting
+    public PowerStats.Descriptor getPowerStatsDescriptor() {
+        ensureInitialized();
+
+        return mPowerStatsDescriptor;
+    }
+
     @Override
     protected PowerStats collectStats() {
+        ensureInitialized();
+
+        if (!mIsPerUidTimeInStateSupported) {
+            return null;
+        }
+
         mCpuPowerStats.uidStats.clear();
-        long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(
-                this::processUidStats, mScalingStepToPowerBracketMap, mLastUpdateTimestampNanos,
-                mTempUidStats);
+        // TODO(b/305120724): additionally retrieve time-in-cluster for each CPU cluster
+        long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(this::processUidStats,
+                mLayout.getScalingStepToPowerBracketMap(), mLastUpdateTimestampNanos,
+                mTempCpuTimeByScalingStep, mTempUidStats);
+        for (int step = mLayout.getCpuScalingStepCount() - 1; step >= 0; step--) {
+            mLayout.setTimeByScalingStep(mCpuPowerStats.stats, step,
+                    mTempCpuTimeByScalingStep[step] - mCpuTimeByScalingStep[step]);
+            mCpuTimeByScalingStep[step] = mTempCpuTimeByScalingStep[step];
+        }
+
         mCpuPowerStats.durationMs =
                 (newTimestampNanos - mLastUpdateTimestampNanos) / NANOS_PER_MILLIS;
         mLastUpdateTimestampNanos = newTimestampNanos;
+
+        long uptimeMillis = mClock.uptimeMillis();
+        long uptimeDelta = uptimeMillis - mLastUpdateUptimeMillis;
+        mLastUpdateUptimeMillis = uptimeMillis;
+
+        if (uptimeDelta > mCpuPowerStats.durationMs) {
+            uptimeDelta = mCpuPowerStats.durationMs;
+        }
+        mLayout.setUptime(mCpuPowerStats.stats, uptimeDelta);
+
+        if (mCpuEnergyConsumerIds.length != 0) {
+            collectEnergyConsumers();
+        }
+
         return mCpuPowerStats;
     }
 
+    private void collectEnergyConsumers() {
+        int voltageMv = mVoltageSupplier.getAsInt();
+        if (voltageMv <= 0) {
+            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+                          + " mV) when querying energy consumers");
+            return;
+        }
+
+        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+        mLastVoltageMv = voltageMv;
+
+        CompletableFuture<EnergyConsumerResult[]> future =
+                mPowerStatsInternal.getEnergyConsumedAsync(mCpuEnergyConsumerIds);
+        EnergyConsumerResult[] results = null;
+        try {
+            results = future.get(
+                    POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e);
+        }
+        if (results == null) {
+            return;
+        }
+
+        for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) {
+            int id = mCpuEnergyConsumerIds[i];
+            for (EnergyConsumerResult result : results) {
+                if (result.id == id) {
+                    long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
+                            ? result.energyUWs - mLastConsumedEnergyUws[i] : 0;
+                    if (energyDelta < 0) {
+                        // Likely, restart of powerstats HAL
+                        energyDelta = 0;
+                    }
+                    mLayout.setConsumedEnergy(mCpuPowerStats.stats, i,
+                            uJtoUc(energyDelta, averageVoltage));
+                    mLastConsumedEnergyUws[i] = result.energyUWs;
+                    break;
+                }
+            }
+        }
+    }
+
     @VisibleForNative
     interface KernelCpuStatsCallback {
         @Keep // Called from native
-        void processUidStats(int uid, long[] stats);
+        void processUidStats(int uid, long[] timeByPowerBracket);
     }
 
-    private void processUidStats(int uid, long[] stats) {
+    private void processUidStats(int uid, long[] timeByPowerBracket) {
+        int powerBracketCount = mLayout.getCpuPowerBracketCount();
+
         UidStats uidStats = mUidStats.get(uid);
         if (uidStats == null) {
             uidStats = new UidStats();
-            uidStats.stats = new long[mUidStatsSize];
-            uidStats.delta = new long[mUidStatsSize];
+            uidStats.timeByPowerBracket = new long[powerBracketCount];
+            uidStats.stats = new long[mLayout.getUidStatsArrayLength()];
             mUidStats.put(uid, uidStats);
         }
 
         boolean nonzero = false;
-        for (int i = mUidStatsSize - 1; i >= 0; i--) {
-            long delta = uidStats.delta[i] = stats[i] - uidStats.stats[i];
+        for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) {
+            long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket];
             if (delta != 0) {
                 nonzero = true;
             }
-            uidStats.stats[i] = stats[i];
+            mLayout.setUidTimeByPowerBracket(uidStats.stats, bracket, delta);
+            uidStats.timeByPowerBracket[bracket] = timeByPowerBracket[bracket];
         }
         if (nonzero) {
-            mCpuPowerStats.uidStats.put(uid, uidStats.delta);
+            mCpuPowerStats.uidStats.put(uid, uidStats.stats);
         }
     }
 
@@ -128,13 +765,15 @@
      * Native class that retrieves CPU stats from the kernel.
      */
     public static class KernelCpuStatsReader {
+        protected native boolean nativeIsSupportedFeature();
+
         protected native long nativeReadCpuStats(KernelCpuStatsCallback callback,
                 int[] scalingStepToPowerBracketMap, long lastUpdateTimestampNanos,
-                long[] tempForUidStats);
+                long[] outCpuTimeByScalingStep, long[] tempForUidStats);
     }
 
     private static class UidStats {
         public long[] stats;
-        public long[] delta;
+        public long[] timeByPowerBracket;
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 686268f..2c7843e 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -16,12 +16,20 @@
 
 package com.android.server.power.stats;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
 import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.util.Collection;
 
 /**
@@ -31,9 +39,17 @@
  * as part of the {@link PowerStats.Descriptor}.
  */
 class PowerComponentAggregatedPowerStats {
+    static final String XML_TAG_POWER_COMPONENT = "power_component";
+    static final String XML_ATTR_ID = "id";
+    private static final String XML_TAG_DEVICE_STATS = "device-stats";
+    private static final String XML_TAG_UID_STATS = "uid-stats";
+    private static final String XML_ATTR_UID = "uid";
+
     public final int powerComponentId;
     private final MultiStateStats.States[] mDeviceStateConfig;
     private final MultiStateStats.States[] mUidStateConfig;
+    @NonNull
+    private final AggregatedPowerStatsConfig.PowerComponent mConfig;
     private final int[] mDeviceStates;
     private final long[] mDeviceStateTimestamps;
 
@@ -49,22 +65,31 @@
         public MultiStateStats stats;
     }
 
-    PowerComponentAggregatedPowerStats(int powerComponentId,
-            MultiStateStats.States[] deviceStates,
-            MultiStateStats.States[] uidStates) {
-        this.powerComponentId = powerComponentId;
-        mDeviceStateConfig = deviceStates;
-        mUidStateConfig = uidStates;
+    PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+        mConfig = config;
+        powerComponentId = config.getPowerComponentId();
+        mDeviceStateConfig = config.getDeviceStateConfig();
+        mUidStateConfig = config.getUidStateConfig();
         mDeviceStates = new int[mDeviceStateConfig.length];
         mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
     }
 
-    void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+    @NonNull
+    public AggregatedPowerStatsConfig.PowerComponent getConfig() {
+        return mConfig;
+    }
+
+    @Nullable
+    public PowerStats.Descriptor getPowerStatsDescriptor() {
+        return mPowerStatsDescriptor;
+    }
+
+    void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
         mDeviceStates[stateId] = state;
         mDeviceStateTimestamps[stateId] = time;
 
         if (mDeviceStateConfig[stateId].isTracked()) {
-            if (mDeviceStats != null || createDeviceStats()) {
+            if (mDeviceStats != null) {
                 mDeviceStats.setState(stateId, state, time);
             }
         }
@@ -72,14 +97,14 @@
         if (mUidStateConfig[stateId].isTracked()) {
             for (int i = mUidStats.size() - 1; i >= 0; i--) {
                 PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
-                if (uidStats.stats != null || createUidStats(uidStats)) {
+                if (uidStats.stats != null) {
                     uidStats.stats.setState(stateId, state, time);
                 }
             }
         }
     }
 
-    void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+    void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
             long time) {
         if (!mUidStateConfig[stateId].isTracked()) {
             return;
@@ -89,11 +114,21 @@
         uidStats.states[stateId] = state;
         uidStats.stateTimestampMs[stateId] = time;
 
-        if (uidStats.stats != null || createUidStats(uidStats)) {
+        if (uidStats.stats != null) {
             uidStats.stats.setState(stateId, state, time);
         }
     }
 
+    void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) {
+        mDeviceStats.setStats(states, values);
+    }
+
+    void setUidStats(int uid, @AggregatedPowerStatsConfig.TrackedState int[] states,
+            long[] values) {
+        UidStats uidStats = getUidStats(uid);
+        uidStats.stats.setStats(states, values);
+    }
+
     boolean isCompatible(PowerStats powerStats) {
         return mPowerStatsDescriptor == null || mPowerStatsDescriptor.equals(powerStats.descriptor);
     }
@@ -102,13 +137,6 @@
         mPowerStatsDescriptor = powerStats.descriptor;
 
         if (mDeviceStats == null) {
-            if (mStatsFactory == null) {
-                mStatsFactory = new MultiStateStats.Factory(
-                        mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
-                mUidStatsFactory = new MultiStateStats.Factory(
-                        mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
-            }
-
             createDeviceStats();
         }
 
@@ -183,7 +211,11 @@
 
     private boolean createDeviceStats() {
         if (mStatsFactory == null) {
-            return false;
+            if (mPowerStatsDescriptor == null) {
+                return false;
+            }
+            mStatsFactory = new MultiStateStats.Factory(
+                    mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
         }
 
         mDeviceStats = mStatsFactory.create();
@@ -196,7 +228,11 @@
 
     private boolean createUidStats(UidStats uidStats) {
         if (mUidStatsFactory == null) {
-            return false;
+            if (mPowerStatsDescriptor == null) {
+                return false;
+            }
+            mUidStatsFactory = new MultiStateStats.Factory(
+                    mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
         }
 
         uidStats.stats = mUidStatsFactory.create();
@@ -211,11 +247,80 @@
         return true;
     }
 
+    public void writeXml(TypedXmlSerializer serializer) throws IOException {
+        // No stats aggregated - can skip writing XML altogether
+        if (mPowerStatsDescriptor == null) {
+            return;
+        }
+
+        serializer.startTag(null, XML_TAG_POWER_COMPONENT);
+        serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+        mPowerStatsDescriptor.writeXml(serializer);
+
+        if (mDeviceStats != null) {
+            serializer.startTag(null, XML_TAG_DEVICE_STATS);
+            mDeviceStats.writeXml(serializer);
+            serializer.endTag(null, XML_TAG_DEVICE_STATS);
+        }
+
+        for (int i = mUidStats.size() - 1; i >= 0; i--) {
+            int uid = mUidStats.keyAt(i);
+            UidStats uidStats = mUidStats.valueAt(i);
+            if (uidStats.stats != null) {
+                serializer.startTag(null, XML_TAG_UID_STATS);
+                serializer.attributeInt(null, XML_ATTR_UID, uid);
+                uidStats.stats.writeXml(serializer);
+                serializer.endTag(null, XML_TAG_UID_STATS);
+            }
+        }
+
+        serializer.endTag(null, XML_TAG_POWER_COMPONENT);
+        serializer.flush();
+    }
+
+    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case PowerStats.Descriptor.XML_TAG_DESCRIPTOR:
+                        mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser);
+                        if (mPowerStatsDescriptor == null) {
+                            return false;
+                        }
+                        break;
+                    case XML_TAG_DEVICE_STATS:
+                        if (mDeviceStats == null) {
+                            createDeviceStats();
+                        }
+                        if (!mDeviceStats.readFromXml(parser)) {
+                            return false;
+                        }
+                        break;
+                    case XML_TAG_UID_STATS:
+                        int uid = parser.getAttributeInt(null, XML_ATTR_UID);
+                        UidStats uidStats = getUidStats(uid);
+                        if (uidStats.stats == null) {
+                            createUidStats(uidStats);
+                        }
+                        if (!uidStats.stats.readFromXml(parser)) {
+                            return false;
+                        }
+                        break;
+                }
+            }
+            eventType = parser.next();
+        }
+        return true;
+    }
+
     void dumpDevice(IndentingPrintWriter ipw) {
         if (mDeviceStats != null) {
             ipw.println(mPowerStatsDescriptor.name);
             ipw.increaseIndent();
-            mDeviceStats.dump(ipw);
+            mDeviceStats.dump(ipw, stats ->
+                    mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats));
             ipw.decreaseIndent();
         }
     }
@@ -225,7 +330,8 @@
         if (uidStats != null && uidStats.stats != null) {
             ipw.println(mPowerStatsDescriptor.name);
             ipw.increaseIndent();
-            uidStats.stats.dump(ipw);
+            uidStats.stats.dump(ipw, stats ->
+                    mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats));
             ipw.decreaseIndent();
         }
     }
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 6a1c1da..2f9d567 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -15,59 +15,33 @@
  */
 package com.android.server.power.stats;
 
-import android.annotation.IntDef;
-import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.util.SparseArray;
 
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
-import com.android.internal.os.MultiStateStats;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.function.Consumer;
 
-class PowerStatsAggregator {
-    public static final int STATE_POWER = 0;
-    public static final int STATE_SCREEN = 1;
-    public static final int STATE_PROCESS_STATE = 2;
-
-    @IntDef({
-            STATE_POWER,
-            STATE_SCREEN,
-            STATE_PROCESS_STATE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TrackedState {
-    }
-
-    static final int POWER_STATE_BATTERY = 0;
-    static final int POWER_STATE_OTHER = 1;   // Plugged in, or on wireless charger, etc.
-    static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
-
-    static final int SCREEN_STATE_ON = 0;
-    static final int SCREEN_STATE_OTHER = 1;  // Off, doze etc
-    static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
-
-    static final String[] STATE_LABELS_PROCESS_STATE;
-
-    static {
-        String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
-        for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
-            procStateLabels[i] = BatteryConsumer.processStateToString(i);
-        }
-        STATE_LABELS_PROCESS_STATE = procStateLabels;
-    }
-
-    private final BatteryStatsHistory mHistory;
+/**
+ * Power stats aggregator. It reads through portions of battery stats history, finds
+ * relevant items (state changes, power stats etc) and produces one or more
+ * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
+ */
+public class PowerStatsAggregator {
     private final AggregatedPowerStats mStats;
+    private final BatteryStatsHistory mHistory;
+    private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>();
 
-    private PowerStatsAggregator(BatteryStatsHistory history,
-            AggregatedPowerStats aggregatedPowerStats) {
+    public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
+            BatteryStatsHistory history) {
+        mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
         mHistory = history;
-        mStats = aggregatedPowerStats;
+        for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig :
+                aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) {
+            AggregatedPowerStatsProcessor processor = powerComponentsConfig.getProcessor();
+            mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor);
+        }
     }
 
     /**
@@ -82,12 +56,10 @@
      * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
      * the stats in the <code>accept</code> method and never cache it.
      */
-    void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+    public void aggregatePowerStats(long startTimeMs, long endTimeMs,
             Consumer<AggregatedPowerStats> consumer) {
-        mStats.reset();
-
-        int currentBatteryState = POWER_STATE_BATTERY;
-        int currentScreenState = SCREEN_STATE_OTHER;
+        int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+        int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
         long baseTime = -1;
         long lastTime = 0;
         try (BatteryStatsHistoryIterator iterator =
@@ -96,131 +68,72 @@
                 BatteryStats.HistoryItem item = iterator.next();
 
                 if (baseTime < 0) {
-                    mStats.setStartTime(item.currentTime);
+                    mStats.addClockUpdate(item.time, item.currentTime);
                     baseTime = item.time;
+                } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
+                           || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
+                    mStats.addClockUpdate(item.time, item.currentTime);
                 }
 
                 lastTime = item.time;
 
                 int batteryState =
                         (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
-                                ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+                                ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
+                                : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
                 if (batteryState != currentBatteryState) {
-                    mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+                    mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
+                            item.time);
                     currentBatteryState = batteryState;
                 }
 
                 int screenState =
                         (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
-                                ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+                                ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
+                                : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
                 if (screenState != currentScreenState) {
-                    mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+                    mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
+                            item.time);
                     currentScreenState = screenState;
                 }
 
                 if (item.processStateChange != null) {
-                    mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+                    mStats.setUidState(item.processStateChange.uid,
+                            AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
                             item.processStateChange.processState, item.time);
                 }
 
                 if (item.powerStats != null) {
                     if (!mStats.isCompatible(item.powerStats)) {
-                        mStats.setDuration(lastTime - baseTime);
-                        consumer.accept(mStats);
+                        if (lastTime > baseTime) {
+                            mStats.setDuration(lastTime - baseTime);
+                            finish(mStats);
+                            consumer.accept(mStats);
+                        }
                         mStats.reset();
-                        mStats.setStartTime(item.currentTime);
+                        mStats.addClockUpdate(item.time, item.currentTime);
                         baseTime = lastTime = item.time;
                     }
                     mStats.addPowerStats(item.powerStats, item.time);
                 }
             }
         }
-        mStats.setDuration(lastTime - baseTime);
-        consumer.accept(mStats);
+        if (lastTime > baseTime) {
+            mStats.setDuration(lastTime - baseTime);
+            finish(mStats);
+            consumer.accept(mStats);
+        }
+
+        mStats.reset();     // to free up memory
     }
 
-    static class Builder {
-        static class PowerComponentAggregateStatsBuilder {
-            private final int mPowerComponentId;
-            private @TrackedState int[] mTrackedDeviceStates;
-            private @TrackedState int[] mTrackedUidStates;
-
-            PowerComponentAggregateStatsBuilder(int powerComponentId) {
-                this.mPowerComponentId = powerComponentId;
+    private void finish(AggregatedPowerStats stats) {
+        for (int i = 0; i < mProcessors.size(); i++) {
+            PowerComponentAggregatedPowerStats component =
+                    stats.getPowerComponentStats(mProcessors.keyAt(i));
+            if (component != null) {
+                mProcessors.valueAt(i).finish(component);
             }
-
-            public PowerComponentAggregateStatsBuilder trackDeviceStates(
-                    @TrackedState int... states) {
-                mTrackedDeviceStates = states;
-                return this;
-            }
-
-            public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
-                mTrackedUidStates = states;
-                return this;
-            }
-
-            private PowerComponentAggregatedPowerStats build() {
-                MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
-                        new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
-                                PowerStatsAggregator.STATE_LABELS_POWER),
-                        new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
-                                PowerStatsAggregator.STATE_LABELS_SCREEN),
-                };
-
-                MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
-                        new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
-                                PowerStatsAggregator.STATE_LABELS_POWER),
-                        new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
-                                PowerStatsAggregator.STATE_LABELS_SCREEN),
-                        new MultiStateStats.States(
-                                isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
-                                PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
-                };
-
-                switch (mPowerComponentId) {
-                    case BatteryConsumer.POWER_COMPONENT_CPU:
-                        return new CpuAggregatedPowerStats(deviceStates, uidStates);
-                    default:
-                        return new PowerComponentAggregatedPowerStats(mPowerComponentId,
-                                deviceStates, uidStates);
-                }
-            }
-
-            private boolean isTracked(int[] trackedStates, int state) {
-                if (trackedStates == null) {
-                    return false;
-                }
-
-                for (int trackedState : trackedStates) {
-                    if (trackedState == state) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        }
-
-        private final BatteryStatsHistory mHistory;
-        private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
-                new ArrayList<>();
-
-        Builder(BatteryStatsHistory history) {
-            mHistory = history;
-        }
-
-        PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
-            PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
-                    powerComponentId);
-            mPowerComponents.add(builder);
-            return builder;
-        }
-
-        PowerStatsAggregator build() {
-            return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
-                    mPowerComponents.stream()
-                            .map(PowerComponentAggregateStatsBuilder::build)
-                            .toArray(PowerComponentAggregatedPowerStats[]::new)));
         }
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index b49c89f..84cc21e 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -38,8 +38,9 @@
  * except where noted.
  */
 public abstract class PowerStatsCollector {
+    private static final int MILLIVOLTS_PER_VOLT = 1000;
     private final Handler mHandler;
-    private final Clock mClock;
+    protected final Clock mClock;
     private final long mThrottlePeriodMs;
     private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
     private boolean mEnabled;
@@ -100,6 +101,9 @@
     @SuppressWarnings("GuardedBy")  // Field is volatile
     private void collectAndDeliverStats() {
         PowerStats stats = collectStats();
+        if (stats == null) {
+            return;
+        }
         for (Consumer<PowerStats> consumer : mConsumerList) {
             consumer.accept(stats);
         }
@@ -180,4 +184,11 @@
         mHandler.post(done::open);
         done.block();
     }
+
+    /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
+    protected long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
+        // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
+        // since the last snapshot. Round off to the nearest whole long.
+        return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv;
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
new file mode 100644
index 0000000..551302e
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.annotation.DurationMillisLong;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
+ * {@link PowerStatsStore}.
+ */
+public class PowerStatsScheduler {
+    private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
+    private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+    private final Context mContext;
+    private boolean mEnablePeriodicPowerStatsCollection;
+    @DurationMillisLong
+    private final long mAggregatedPowerStatsSpanDuration;
+    @DurationMillisLong
+    private final long mPowerStatsAggregationPeriod;
+    private final PowerStatsStore mPowerStatsStore;
+    private final Clock mClock;
+    private final MonotonicClock mMonotonicClock;
+    private final Handler mHandler;
+    private final BatteryStatsImpl mBatteryStats;
+    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private final PowerStatsAggregator mPowerStatsAggregator;
+    private long mLastSavedSpanEndMonotonicTime;
+
+    public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+            @DurationMillisLong long aggregatedPowerStatsSpanDuration,
+            @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
+            Clock clock, MonotonicClock monotonicClock, Handler handler,
+            BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) {
+        mContext = context;
+        mPowerStatsAggregator = powerStatsAggregator;
+        mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
+        mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
+        mPowerStatsStore = powerStatsStore;
+        mClock = clock;
+        mMonotonicClock = monotonicClock;
+        mHandler = handler;
+        mBatteryStats = batteryStats;
+        mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+    }
+
+    /**
+     * Kicks off the scheduling of power stats aggregation spans.
+     */
+    public void start(boolean enablePeriodicPowerStatsCollection) {
+        mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset);
+        mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
+        if (mEnablePeriodicPowerStatsCollection) {
+            scheduleNextPowerStatsAggregation();
+        }
+    }
+
+    private void scheduleNextPowerStatsAggregation() {
+        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+        alarmManager.set(AlarmManager.ELAPSED_REALTIME,
+                mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+                () -> {
+                    schedulePowerStatsAggregation();
+                    mHandler.post(this::scheduleNextPowerStatsAggregation);
+                }, mHandler);
+    }
+
+    /**
+     * Initiate an asynchronous process of aggregation of power stats.
+     */
+    @VisibleForTesting
+    public void schedulePowerStatsAggregation() {
+        // Catch up the power stats collectors
+        mBatteryStats.schedulePowerStatsSampleCollection();
+        mHandler.post(this::aggregateAndStorePowerStats);
+    }
+
+    private void aggregateAndStorePowerStats() {
+        long currentTimeMillis = mClock.currentTimeMillis();
+        long currentMonotonicTime = mMonotonicClock.monotonicTime();
+        long startTime = getLastSavedSpanEndMonotonicTime();
+        if (startTime < 0) {
+            startTime = mBatteryStats.getHistory().getStartTime();
+        }
+        long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
+                mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
+        while (endTimeMs <= currentMonotonicTime) {
+            mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
+                    stats -> {
+                        storeAggregatedPowerStats(stats);
+                        mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
+                    });
+
+            startTime = endTimeMs;
+            endTimeMs += mAggregatedPowerStatsSpanDuration;
+        }
+    }
+
+    /**
+     * Performs a power stats aggregation pass and then dumps all stored aggregated power stats
+     * spans followed by the remainder that has not been stored yet.
+     */
+    public void aggregateAndDumpPowerStats(PrintWriter pw) {
+        if (mHandler.getLooper().isCurrentThread()) {
+            throw new IllegalStateException("Should not be executed on the bg handler thread.");
+        }
+
+        schedulePowerStatsAggregation();
+
+        // Wait for the aggregation process to finish storing aggregated stats spans in the store.
+        awaitCompletion();
+
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        mHandler.post(() -> {
+            mPowerStatsStore.dump(ipw);
+            // Aggregate the remainder of power stats and dump the results without storing them yet.
+            long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
+            mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0,
+                    stats -> {
+                        // Create a PowerStatsSpan for consistency of the textual output
+                        PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
+                        if (span != null) {
+                            span.dump(ipw);
+                        }
+                    });
+        });
+
+        awaitCompletion();
+    }
+
+    /**
+     * Align the supplied time to the wall clock, for aesthetic purposes. For example, if
+     * the schedule is configured with a 15-min interval, the captured aggregated stats will
+     * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current
+     * time is used for the alignment, so if the wall clock changed during an aggregation span,
+     * or if the device was off (which stops the monotonic clock), the alignment may be
+     * temporarily broken.
+     */
+    @VisibleForTesting
+    public static long alignToWallClock(long targetMonotonicTime, long interval,
+            long currentMonotonicTime, long currentTimeMillis) {
+
+        // Estimate the wall clock time for the requested targetMonotonicTime
+        long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime);
+
+        if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) {
+            // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc
+
+            // First, round up to the next whole minute
+            Calendar cal = Calendar.getInstance();
+            cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1);
+            cal.set(Calendar.SECOND, 0);
+            cal.set(Calendar.MILLISECOND, 0);
+
+            // Now set the minute to a multiple of the requested interval
+            int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS);
+            cal.set(Calendar.MINUTE,
+                    ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes)
+                            * intervalInMinutes);
+
+            long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+            return targetMonotonicTime + adjustment;
+        } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) {
+            // If the interval is a divisor of a day, e.g. 2h, 3h, etc
+
+            // First, round up to the next whole hour
+            Calendar cal = Calendar.getInstance();
+            cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1);
+            cal.set(Calendar.MINUTE, 0);
+            cal.set(Calendar.SECOND, 0);
+            cal.set(Calendar.MILLISECOND, 0);
+
+            // Now set the hour of day to a multiple of the requested interval
+            int intervalInHours = (int) (interval / HOUR_IN_MILLIS);
+            cal.set(Calendar.HOUR_OF_DAY,
+                    ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours)
+                    * intervalInHours);
+
+            long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+            return targetMonotonicTime + adjustment;
+        }
+
+        return targetMonotonicTime;
+    }
+
+    private long getLastSavedSpanEndMonotonicTime() {
+        if (mLastSavedSpanEndMonotonicTime != 0) {
+            return mLastSavedSpanEndMonotonicTime;
+        }
+
+        mLastSavedSpanEndMonotonicTime = -1;
+        for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
+            if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+                for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
+                    long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
+                    if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
+                        mLastSavedSpanEndMonotonicTime = endMonotonicTime;
+                    }
+                }
+            }
+        }
+        return mLastSavedSpanEndMonotonicTime;
+    }
+
+    private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+        mPowerStatsStore.storeAggregatedPowerStats(stats);
+    }
+
+    private void storeBatteryUsageStatsOnReset(int resetReason) {
+        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
+            return;
+        }
+
+        final BatteryUsageStats batteryUsageStats =
+                mBatteryUsageStatsProvider.getBatteryUsageStats(
+                        new BatteryUsageStatsQuery.Builder()
+                                .setMaxStatsAgeMs(0)
+                                .includePowerModels()
+                                .includeProcessStateData()
+                                .build());
+
+        // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+        // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+        // start time
+        long monotonicStartTime =
+                mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+        mHandler.post(() ->
+                mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
+    }
+
+    private void awaitCompletion() {
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
new file mode 100644
index 0000000..3b260ca
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import com.google.android.collect.Sets;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Contains power stats of various kinds, aggregated over a time span.
+ */
+public class PowerStatsSpan {
+    private static final String TAG = "PowerStatsStore";
+
+    /**
+     * Increment VERSION when the XML format of the store changes. Also, update
+     * {@link #isCompatibleXmlFormat} to return true for all legacy versions
+     * that are compatible with the new one.
+     */
+    private static final int VERSION = 1;
+
+    private static final String XML_TAG_METADATA = "metadata";
+    private static final String XML_ATTR_ID = "id";
+    private static final String XML_ATTR_VERSION = "version";
+    private static final String XML_TAG_TIMEFRAME = "timeframe";
+    private static final String XML_ATTR_MONOTONIC = "monotonic";
+    private static final String XML_ATTR_START_TIME = "start";
+    private static final String XML_ATTR_DURATION = "duration";
+    private static final String XML_TAG_SECTION = "section";
+    private static final String XML_ATTR_SECTION_TYPE = "type";
+
+    private static final DateTimeFormatter DATE_FORMAT =
+            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+    static class TimeFrame {
+        public final long startMonotonicTime;
+        @CurrentTimeMillisLong
+        public final long startTime;
+        @DurationMillisLong
+        public final long duration;
+
+        TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime,
+                @DurationMillisLong long duration) {
+            this.startMonotonicTime = startMonotonicTime;
+            this.startTime = startTime;
+            this.duration = duration;
+        }
+
+        void write(TypedXmlSerializer serializer) throws IOException {
+            serializer.startTag(null, XML_TAG_TIMEFRAME);
+            serializer.attributeLong(null, XML_ATTR_START_TIME, startTime);
+            serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime);
+            serializer.attributeLong(null, XML_ATTR_DURATION, duration);
+            serializer.endTag(null, XML_TAG_TIMEFRAME);
+        }
+
+        static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException {
+            return new TimeFrame(
+                    parser.getAttributeLong(null, XML_ATTR_MONOTONIC),
+                    parser.getAttributeLong(null, XML_ATTR_START_TIME),
+                    parser.getAttributeLong(null, XML_ATTR_DURATION));
+        }
+
+        /**
+         * Prints the contents of this TimeFrame.
+         */
+        public void dump(IndentingPrintWriter pw) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime)))
+                    .append(" (monotonic=").append(startMonotonicTime).append(") ")
+                    .append(" duration=");
+            String durationString = TimeUtils.formatDuration(duration);
+            if (durationString.startsWith("+")) {
+                sb.append(durationString.substring(1));
+            } else {
+                sb.append(durationString);
+            }
+            pw.print(sb);
+        }
+    }
+
+    static class Metadata {
+        static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId);
+
+        private final long mId;
+        private final List<TimeFrame> mTimeFrames = new ArrayList<>();
+        private final List<String> mSections = new ArrayList<>();
+
+        Metadata(long id) {
+            mId = id;
+        }
+
+        public long getId() {
+            return mId;
+        }
+
+        public List<TimeFrame> getTimeFrames() {
+            return mTimeFrames;
+        }
+
+        public List<String> getSections() {
+            return mSections;
+        }
+
+        void addTimeFrame(TimeFrame timeFrame) {
+            mTimeFrames.add(timeFrame);
+        }
+
+        void addSection(String sectionType) {
+            // The number of sections per span is small, so there is no need to use a Set
+            if (!mSections.contains(sectionType)) {
+                mSections.add(sectionType);
+            }
+        }
+
+        void write(TypedXmlSerializer serializer) throws IOException {
+            serializer.startTag(null, XML_TAG_METADATA);
+            serializer.attributeLong(null, XML_ATTR_ID, mId);
+            serializer.attributeInt(null, XML_ATTR_VERSION, VERSION);
+            for (TimeFrame timeFrame : mTimeFrames) {
+                timeFrame.write(serializer);
+            }
+            for (String section : mSections) {
+                serializer.startTag(null, XML_TAG_SECTION);
+                serializer.attribute(null, XML_ATTR_SECTION_TYPE, section);
+                serializer.endTag(null, XML_TAG_SECTION);
+            }
+            serializer.endTag(null, XML_TAG_METADATA);
+        }
+
+        /**
+         * Reads just the header of the XML file containing metadata.
+         * Returns null if the file does not contain a compatible &lt;metadata&gt; element.
+         */
+        @Nullable
+        public static Metadata read(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            Metadata metadata = null;
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_METADATA))) {
+                if (eventType == XmlPullParser.START_TAG) {
+                    String tagName = parser.getName();
+                    if (tagName.equals(XML_TAG_METADATA)) {
+                        int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+                        if (!isCompatibleXmlFormat(version)) {
+                            Slog.e(TAG,
+                                    "Incompatible version " + version + "; expected " + VERSION);
+                            return null;
+                        }
+
+                        long id = parser.getAttributeLong(null, XML_ATTR_ID);
+                        metadata = new Metadata(id);
+                    } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) {
+                        metadata.addTimeFrame(TimeFrame.read(parser));
+                    } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) {
+                        metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE));
+                    }
+                }
+                eventType = parser.next();
+            }
+            return metadata;
+        }
+
+        /**
+         * Prints the metadata.
+         */
+        public void dump(IndentingPrintWriter pw) {
+            dump(pw, true);
+        }
+
+        void dump(IndentingPrintWriter pw, boolean includeSections) {
+            pw.print("Span ");
+            if (mTimeFrames.size() > 0) {
+                mTimeFrames.get(0).dump(pw);
+                pw.println();
+            }
+
+            // Sometimes, when the wall clock is adjusted in the middle of a stats session,
+            // we will have more than one time frame.
+            for (int i = 1; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                pw.print("     ");      // Aligned below "Span "
+                timeFrame.dump(pw);
+                pw.println();
+            }
+
+            if (includeSections) {
+                pw.increaseIndent();
+                for (String section : mSections) {
+                    pw.print("section", section);
+                    pw.println();
+                }
+                pw.decreaseIndent();
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+            ipw.print("id", mId);
+            for (int i = 0; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                ipw.print("timeframe=[");
+                timeFrame.dump(ipw);
+                ipw.print("] ");
+            }
+            for (String section : mSections) {
+                ipw.print("section", section);
+            }
+            ipw.flush();
+            return sw.toString().trim();
+        }
+    }
+
+    /**
+     * Contains a specific type of aggregate power stats.  The contents type is determined by
+     * the section type.
+     */
+    public abstract static class Section {
+        private final String mType;
+
+        Section(String type) {
+            mType = type;
+        }
+
+        /**
+         * Returns the section type, which determines the type of data stored in the corresponding
+         * section of {@link PowerStatsSpan}
+         */
+        public String getType() {
+            return mType;
+        }
+
+        abstract void write(TypedXmlSerializer serializer) throws IOException;
+
+        /**
+         * Prints the section type.
+         */
+        public void dump(IndentingPrintWriter ipw) {
+            ipw.println(mType);
+        }
+    }
+
+    /**
+     * A universal XML parser for {@link PowerStatsSpan.Section}'s.  It is aware of all
+     * supported section types as well as their corresponding XML formats.
+     */
+    public interface SectionReader {
+        /**
+         * Reads the contents of the section using the parser. The type of the object
+         * read and the corresponding XML format are determined by the section type.
+         */
+        Section read(String sectionType, TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException;
+    }
+
+    private final Metadata mMetadata;
+    private final List<Section> mSections = new ArrayList<>();
+
+    public PowerStatsSpan(long id) {
+        this(new Metadata(id));
+    }
+
+    private PowerStatsSpan(Metadata metadata) {
+        mMetadata = metadata;
+    }
+
+    public Metadata getMetadata() {
+        return mMetadata;
+    }
+
+    public long getId() {
+        return mMetadata.mId;
+    }
+
+    void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
+            @DurationMillisLong long duration) {
+        mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration));
+    }
+
+    void addSection(Section section) {
+        mMetadata.addSection(section.getType());
+        mSections.add(section);
+    }
+
+    @NonNull
+    public List<Section> getSections() {
+        return mSections;
+    }
+
+    private static boolean isCompatibleXmlFormat(int version) {
+        return version == VERSION;
+    }
+
+    /**
+     * Creates an XML file containing the persistent state of the power stats span.
+     */
+    @VisibleForTesting
+    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+        serializer.setOutput(out, StandardCharsets.UTF_8.name());
+        serializer.startDocument(null, true);
+        mMetadata.write(serializer);
+        for (Section section : mSections) {
+            serializer.startTag(null, XML_TAG_SECTION);
+            serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType);
+            section.write(serializer);
+            serializer.endTag(null, XML_TAG_SECTION);
+        }
+        serializer.endDocument();
+    }
+
+    @Nullable
+    static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser,
+            SectionReader sectionReader, String... sectionTypes)
+            throws IOException, XmlPullParserException {
+        Set<String> neededSections = Sets.newArraySet(sectionTypes);
+        boolean selectSections = !neededSections.isEmpty();
+        parser.setInput(in, StandardCharsets.UTF_8.name());
+
+        Metadata metadata = Metadata.read(parser);
+        if (metadata == null) {
+            return null;
+        }
+
+        PowerStatsSpan span = new PowerStatsSpan(metadata);
+        boolean skipSection = false;
+        int nestingLevel = 0;
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (skipSection) {
+                if (eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_SECTION)) {
+                    nestingLevel--;
+                    if (nestingLevel == 0) {
+                        skipSection = false;
+                    }
+                } else if (eventType == XmlPullParser.START_TAG
+                           && parser.getName().equals(XML_TAG_SECTION)) {
+                    nestingLevel++;
+                }
+            } else if (eventType == XmlPullParser.START_TAG) {
+                String tag = parser.getName();
+                if (tag.equals(XML_TAG_SECTION)) {
+                    String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE);
+                    if (!selectSections || neededSections.contains(sectionType)) {
+                        Section section = sectionReader.read(sectionType, parser);
+                        if (section == null) {
+                            if (selectSections) {
+                                throw new XmlPullParserException(
+                                        "Unsupported PowerStatsStore section type: " + sectionType);
+                            } else {
+                                section = new Section(sectionType) {
+                                    @Override
+                                    public void dump(IndentingPrintWriter ipw) {
+                                        ipw.println("Unsupported PowerStatsStore section type: "
+                                                    + sectionType);
+                                    }
+
+                                    @Override
+                                    void write(TypedXmlSerializer serializer) {
+                                    }
+                                };
+                            }
+                        }
+                        span.addSection(section);
+                    } else {
+                        skipSection = true;
+                    }
+                } else if (tag.equals(XML_TAG_METADATA)) {
+                    Metadata.read(parser);
+                }
+            }
+            eventType = parser.next();
+        }
+        return span;
+    }
+
+    /**
+     * Prints the contents of this power stats span.
+     */
+    public void dump(IndentingPrintWriter ipw) {
+        mMetadata.dump(ipw, /* includeSections */ false);
+        for (Section section : mSections) {
+            ipw.increaseIndent();
+            ipw.println(section.mType);
+            section.dump(ipw);
+            ipw.decreaseIndent();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
new file mode 100644
index 0000000..7123bcb
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryUsageStats;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A storage mechanism for aggregated power/battery stats.
+ */
+public class PowerStatsStore {
+    private static final String TAG = "PowerStatsStore";
+
+    private static final String POWER_STATS_DIR = "power-stats";
+    private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss";
+    private static final String DIR_LOCK_FILENAME = ".lock";
+    private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024;
+
+    private final File mSystemDir;
+    private final File mStoreDir;
+    private final File mLockFile;
+    private final ReentrantLock mFileLock = new ReentrantLock();
+    private FileLock mJvmLock;
+    private final long mMaxStorageBytes;
+    private final Handler mHandler;
+    private final PowerStatsSpan.SectionReader mSectionReader;
+    private volatile List<PowerStatsSpan.Metadata> mTableOfContents;
+
+    public PowerStatsStore(@NonNull File systemDir, Handler handler,
+            AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+        this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler,
+                new DefaultSectionReader(aggregatedPowerStatsConfig));
+    }
+
+    @VisibleForTesting
+    public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler,
+            @NonNull PowerStatsSpan.SectionReader sectionReader) {
+        mSystemDir = systemDir;
+        mStoreDir = new File(systemDir, POWER_STATS_DIR);
+        mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
+        mHandler = handler;
+        mMaxStorageBytes = maxStorageBytes;
+        mSectionReader = sectionReader;
+        mHandler.post(this::maybeClearLegacyStore);
+    }
+
+    /**
+     * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store.
+     */
+    @NonNull
+    public List<PowerStatsSpan.Metadata> getTableOfContents() {
+        List<PowerStatsSpan.Metadata> toc = mTableOfContents;
+        if (toc != null) {
+            return toc;
+        }
+
+        TypedXmlPullParser parser = Xml.newBinaryPullParser();
+        lockStoreDirectory();
+        try {
+            toc = new ArrayList<>();
+            for (File file : mStoreDir.listFiles()) {
+                String fileName = file.getName();
+                if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+                    continue;
+                }
+                try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+                    parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+                    PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser);
+                    if (metadata != null) {
+                        toc.add(metadata);
+                    } else {
+                        Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName);
+                        file.delete();
+                    }
+                } catch (IOException | XmlPullParserException e) {
+                    Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName);
+                }
+            }
+            toc.sort(PowerStatsSpan.Metadata.COMPARATOR);
+            mTableOfContents = Collections.unmodifiableList(toc);
+        } finally {
+            unlockStoreDirectory();
+        }
+
+        return toc;
+    }
+
+    /**
+     * Saves the specified span in the store.
+     */
+    public void storePowerStatsSpan(PowerStatsSpan span) {
+        maybeClearLegacyStore();
+        lockStoreDirectory();
+        try {
+            if (!mStoreDir.exists()) {
+                if (!mStoreDir.mkdirs()) {
+                    Slog.e(TAG, "Could not create a directory for power stats store");
+                    return;
+                }
+            }
+
+            AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId()));
+            file.write(out-> {
+                try {
+                    span.writeXml(out, Xml.newBinarySerializer());
+                } catch (Exception e) {
+                    // AtomicFile will log the exception and delete the file.
+                    throw new RuntimeException(e);
+                }
+            });
+            mTableOfContents = null;
+            removeOldSpansLocked();
+        } finally {
+            unlockStoreDirectory();
+        }
+    }
+
+    /**
+     * Loads the PowerStatsSpan identified by its ID. Only loads the sections with
+     * the specified types.  Loads all sections if no sectionTypes is empty.
+     */
+    @Nullable
+    public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) {
+        TypedXmlPullParser parser = Xml.newBinaryPullParser();
+        lockStoreDirectory();
+        try {
+            File file = makePowerStatsSpanFilename(id);
+            try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+                return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes);
+            } catch (IOException | XmlPullParserException e) {
+                Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file);
+            }
+        } finally {
+            unlockStoreDirectory();
+        }
+        return null;
+    }
+
+    void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+        PowerStatsSpan span = createPowerStatsSpan(stats);
+        if (span == null) {
+            return;
+        }
+        storePowerStatsSpan(span);
+    }
+
+    static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
+        List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+        if (clockUpdates.isEmpty()) {
+            Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
+            return null;
+        }
+
+        long monotonicTime = clockUpdates.get(0).monotonicTime;
+        long durationSum = 0;
+        PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
+        for (int i = 0; i < clockUpdates.size(); i++) {
+            AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
+            long duration;
+            if (i == clockUpdates.size() - 1) {
+                duration = stats.getDuration() - durationSum;
+            } else {
+                duration = clockUpdate.monotonicTime - monotonicTime;
+            }
+            span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
+            monotonicTime = clockUpdate.monotonicTime;
+            durationSum += duration;
+        }
+
+        span.addSection(new AggregatedPowerStatsSection(stats));
+        return span;
+    }
+
+    /**
+     * Stores a {@link PowerStatsSpan} containing a single section for the supplied
+     * battery usage stats.
+     */
+    public void storeBatteryUsageStats(long monotonicStartTime,
+            BatteryUsageStats batteryUsageStats) {
+        PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+        span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+                batteryUsageStats.getStatsDuration());
+        span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+        storePowerStatsSpan(span);
+    }
+
+    /**
+     * Creates a file name by formatting the span ID as a 19-digit zero-padded number.
+     * This ensures that the lexicographically sorted directory follows the chronological order.
+     */
+    private File makePowerStatsSpanFilename(long id) {
+        return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id)
+                                   + POWER_STATS_SPAN_FILE_EXTENSION);
+    }
+
+    private void maybeClearLegacyStore() {
+        File legacyStoreDir = new File(mSystemDir, "battery-usage-stats");
+        if (legacyStoreDir.exists()) {
+            FileUtils.deleteContentsAndDir(legacyStoreDir);
+        }
+    }
+
+    private void lockStoreDirectory() {
+        mFileLock.lock();
+
+        // Lock the directory from access by other JVMs
+        try {
+            mLockFile.getParentFile().mkdirs();
+            mLockFile.createNewFile();
+            mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
+        } catch (IOException e) {
+            Slog.e(TAG, "Cannot lock snapshot directory", e);
+        }
+    }
+
+    private void unlockStoreDirectory() {
+        try {
+            mJvmLock.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Cannot unlock snapshot directory", e);
+        } finally {
+            mFileLock.unlock();
+        }
+    }
+
+    private void removeOldSpansLocked() {
+        // Read the directory list into a _sorted_ map.  The alphanumeric ordering
+        // corresponds to the historical order of snapshots because the file names
+        // are timestamps zero-padded to the same length.
+        long totalSize = 0;
+        TreeMap<File, Long> mFileSizes = new TreeMap<>();
+        for (File file : mStoreDir.listFiles()) {
+            final long fileSize = file.length();
+            totalSize += fileSize;
+            if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+                mFileSizes.put(file, fileSize);
+            }
+        }
+
+        while (totalSize > mMaxStorageBytes) {
+            final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
+            if (entry == null) {
+                break;
+            }
+
+            File file = entry.getKey();
+            if (!file.delete()) {
+                Slog.e(TAG, "Cannot delete power stats span " + file);
+            }
+            totalSize -= entry.getValue();
+            mFileSizes.remove(file);
+            mTableOfContents = null;
+        }
+    }
+
+    /**
+     * Deletes all contents from the store.
+     */
+    public void reset() {
+        lockStoreDirectory();
+        try {
+            for (File file : mStoreDir.listFiles()) {
+                if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+                    if (!file.delete()) {
+                        Slog.e(TAG, "Cannot delete power stats span " + file);
+                    }
+                }
+            }
+            mTableOfContents = List.of();
+        } finally {
+            unlockStoreDirectory();
+        }
+    }
+
+    /**
+     * Prints the summary of contents of the store: only metadata, but not the actual stored
+     * objects.
+     */
+    public void dumpTableOfContents(IndentingPrintWriter ipw) {
+        ipw.println("Power stats store TOC");
+        ipw.increaseIndent();
+        List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+        for (PowerStatsSpan.Metadata metadata : contents) {
+            metadata.dump(ipw);
+        }
+        ipw.decreaseIndent();
+    }
+
+    /**
+     * Prints the contents of the store.
+     */
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("Power stats store");
+        ipw.increaseIndent();
+        List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+        for (PowerStatsSpan.Metadata metadata : contents) {
+            PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
+            if (span != null) {
+                span.dump(ipw);
+            }
+        }
+        ipw.decreaseIndent();
+    }
+
+    private static class DefaultSectionReader implements PowerStatsSpan.SectionReader {
+        private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+        DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+            mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
+        }
+
+        @Override
+        public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            switch (sectionType) {
+                case AggregatedPowerStatsSection.TYPE:
+                    return new AggregatedPowerStatsSection(
+                            AggregatedPowerStats.createFromXml(parser,
+                                    mAggregatedPowerStatsConfig));
+                case BatteryUsageStatsSection.TYPE:
+                    return new BatteryUsageStatsSection(
+                            BatteryUsageStats.createFromXml(parser));
+                default:
+                    return null;
+            }
+        }
+    }
+}
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 d61bebc..0f13571 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -1,8 +1,15 @@
 package: "com.android.server.power.optimization"
 
 flag {
+    name: "power_monitor_api"
+    namespace: "backstage_power"
+    description: "Feature flag for ODPM API"
+    bug: "295027807"
+}
+
+flag {
     name: "streamlined_battery_stats"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for streamlined battery stats"
     bug: "285646152"
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 5609f69..77290fd 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -694,7 +694,7 @@
                     Log.d(TAG, String.format(Locale.ENGLISH,
                             "Monitor=%s timestamp=%d energy=%d"
                                     + " uid=%d noise=%.1f%% returned=%d",
-                            state.powerMonitor.name,
+                            state.powerMonitor.getName(),
                             state.timestampMs,
                             state.energyUws,
                             callingUid,
@@ -728,7 +728,7 @@
         }
 
         for (PowerMonitorState powerMonitorState : powerMonitorStates) {
-            if (powerMonitorState.powerMonitor.type
+            if (powerMonitorState.powerMonitor.getType()
                     == PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {
                 for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
                     if (energyConsumerResult.id == powerMonitorState.id) {
@@ -754,7 +754,7 @@
         }
 
         for (PowerMonitorState powerMonitorState : powerMonitorStates) {
-            if (powerMonitorState.powerMonitor.type
+            if (powerMonitorState.powerMonitor.getType()
                     == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
                 for (EnergyMeasurement energyMeasurement : energyMeasurements) {
                     if (energyMeasurement.id == powerMonitorState.id) {
@@ -773,7 +773,7 @@
             @PowerMonitor.PowerMonitorType int type) {
         int count = 0;
         for (PowerMonitorState monitorState : powerMonitorStates) {
-            if (monitorState.powerMonitor.type == type) {
+            if (monitorState.powerMonitor.getType() == type) {
                 count++;
             }
         }
@@ -785,7 +785,7 @@
         int[] ids = new int[count];
         int index = 0;
         for (PowerMonitorState monitorState : powerMonitorStates) {
-            if (monitorState.powerMonitor.type == type) {
+            if (monitorState.powerMonitor.getType() == type) {
                 ids[index++] = monitorState.id;
             }
         }
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index 7b0fe9a..a01bac6 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -266,10 +266,10 @@
     }
 
     /**
-     * @return {@code true} iff. {@code userId} is locked on an FBE device.
+     * @return {@code true} iff the credential-encrypted storage for {@code userId} is locked.
      */
     @VisibleForTesting
     public boolean isUserCredentialLocked(int userId) {
-        return StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId);
+        return StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId);
     }
 }
diff --git a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
index c908acd..d5bc912 100644
--- a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
+++ b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
@@ -24,9 +24,10 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.security.keymaster.IKeyAttestationApplicationIdProvider;
-import android.security.keymaster.KeyAttestationApplicationId;
-import android.security.keymaster.KeyAttestationPackageInfo;
+import android.security.keystore.IKeyAttestationApplicationIdProvider;
+import android.security.keystore.KeyAttestationApplicationId;
+import android.security.keystore.KeyAttestationPackageInfo;
+import android.security.keystore.Signature;
 
 /**
  * @hide
@@ -64,14 +65,25 @@
             for (int i = 0; i < packageNames.length; ++i) {
                 PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageNames[i],
                         PackageManager.GET_SIGNATURES, userId);
-                keyAttestationPackageInfos[i] = new KeyAttestationPackageInfo(packageNames[i],
-                        packageInfo.getLongVersionCode(), packageInfo.signatures);
+                KeyAttestationPackageInfo pInfo = new KeyAttestationPackageInfo();
+                pInfo.packageName = new String(packageNames[i]);
+                pInfo.versionCode = packageInfo.getLongVersionCode();
+                pInfo.signatures = new Signature[packageInfo.signatures.length];
+                for (int index = 0; index < packageInfo.signatures.length; index++) {
+                    Signature sign = new Signature();
+                    sign.data = packageInfo.signatures[index].toByteArray();
+                    pInfo.signatures[index] = sign;
+                }
+
+                keyAttestationPackageInfos[i] = pInfo;
             }
         } catch (NameNotFoundException nnfe) {
             throw new RemoteException(nnfe.getMessage());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        return new KeyAttestationApplicationId(keyAttestationPackageInfos);
+        KeyAttestationApplicationId attestAppId = new KeyAttestationApplicationId();
+        attestAppId.packageInfos = keyAttestationPackageInfos;
+        return attestAppId;
     }
 }
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index c9db343..0656a6a 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -174,8 +174,6 @@
     private CallStateHelper mCallStateHelper;
     private KeyguardManager mKeyguardManager;
 
-    private SafetyCenterManager mSafetyCenterManager;
-
     private int mCurrentUser = USER_NULL;
 
     public SensorPrivacyService(Context context) {
@@ -191,7 +189,6 @@
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
-        mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
     }
 
     @Override
@@ -656,7 +653,9 @@
             String contentTitle = getUiContext().getString(messageRes);
             Spanned contentText = Html.fromHtml(getUiContext().getString(
                     R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
-            String action = mSafetyCenterManager.isSafetyCenterEnabled()
+            SafetyCenterManager safetyCenterManager =
+                    mContext.getSystemService(SafetyCenterManager.class);
+            String action = safetyCenterManager.isSafetyCenterEnabled()
                     ? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS;
 
             PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bff6d50..6d580e9 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -39,8 +39,10 @@
 import android.speech.RecognitionService;
 import android.speech.SpeechRecognizer;
 import android.util.Slog;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.modules.expresslog.Counter;
 import com.android.server.infra.AbstractPerUserSystemService;
 
 import java.util.HashMap;
@@ -64,6 +66,9 @@
     private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid =
             new HashMap<>();
 
+    @GuardedBy("mLock")
+    private final SparseIntArray mSessionCountByUid = new SparseIntArray();
+
     SpeechRecognitionManagerServiceImpl(
             @NonNull SpeechRecognitionManagerService master,
             @NonNull Object lock, @UserIdInt int userId) {
@@ -216,6 +221,7 @@
             service.shutdown(clientToken);
         }
         synchronized (mLock) {
+            decrementSessionCountForUidLocked(callingUid);
             if (!service.hasActiveSessions()) {
                 removeService(callingUid, service);
             }
@@ -239,6 +245,26 @@
         return ComponentName.unflattenFromString(serviceName);
     }
 
+    @GuardedBy("mLock")
+    private int getSessionCountByUidLocked(int uid) {
+        return mSessionCountByUid.get(uid, 0);
+    }
+
+    @GuardedBy("mLock")
+    private void incrementSessionCountForUidLocked(int uid) {
+        mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1);
+    }
+
+    @GuardedBy("mLock")
+    private void decrementSessionCountForUidLocked(int uid) {
+        int newCount = mSessionCountByUid.get(uid, 1) - 1;
+        if (newCount > 0) {
+            mSessionCountByUid.put(uid, newCount);
+        } else {
+            mSessionCountByUid.delete(uid);
+        }
+    }
+
     private RemoteSpeechRecognitionService createService(
             int callingUid, ComponentName serviceComponent) {
         synchronized (mLock) {
@@ -247,6 +273,18 @@
 
             if (servicesForClient != null
                     && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+                Slog.w(TAG, "Number of remote services exceeded for uid: " + callingUid);
+                Counter.logIncrementWithUid(
+                        "speech_recognition.value_exceed_service_connections_count",
+                        callingUid);
+                return null;
+            }
+
+            if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+                Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid);
+                Counter.logIncrementWithUid(
+                        "speech_recognition.value_exceed_session_count",
+                        callingUid);
                 return null;
             }
 
@@ -262,6 +300,7 @@
                         Slog.i(TAG, "Reused existing connection to " + serviceComponent);
                     }
 
+                    incrementSessionCountForUidLocked(callingUid);
                     return existingService.get();
                 }
             }
@@ -282,6 +321,7 @@
                 Slog.i(TAG, "Creating a new connection to " + serviceComponent);
             }
 
+            incrementSessionCountForUidLocked(callingUid);
             return service;
         }
     }
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3a..c33f3d9 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,11 +1,10 @@
 jeffreyhuang@google.com
 joeo@google.com
-jtnguyen@google.com
+monicamwang@google.com
 muhammadq@google.com
+rayhdez@google.com
 rslawik@google.com
-ruchirr@google.com
 sharaienko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index bfe34049e..9a9b836 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -41,8 +41,8 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 
 import java.io.ByteArrayInputStream;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index a5c0fb3..cddc79d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1047,6 +1047,16 @@
                     // in use frontends when no available frontend has been found.
                     int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
                     if (currentLowestPriority > priority) {
+                        // we need to check the max used num if the target frontend type is not
+                        // currently in primary use (and simply blocked due to exclusive group)
+                        ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
+                        int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+                        FrontendResource primaryFe = getFrontendResource(primaryFeId);
+                        if (fr.getType() != primaryFe.getType()
+                                && isFrontendMaxNumUseReached(fr.getType())) {
+                            continue;
+                        }
+                        // update the target frontend
                         inUseLowestPriorityFrHandle = fr.getHandle();
                         currentLowestPriority = priority;
                         isRequestFromSameProcess = (requestClient.getProcessId()
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index e4f9607..a346216 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.VibratorInfo;
 import android.os.vibrator.persistence.ParsedVibration;
 import android.os.vibrator.persistence.VibrationXmlParser;
@@ -127,6 +128,10 @@
                     VibrationXmlParser.VibrationXmlParserException,
                     XmlParserException,
                     XmlPullParserException {
+        if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
+            Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
+            return null;
+        }
         String customizationFile =
                 res.getString(
                         com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index becbbf2..519acec 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -22,11 +22,10 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
-import android.view.flags.FeatureFlags;
-import android.view.flags.FeatureFlagsImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -56,7 +55,8 @@
     // If present and valid, a vibration here will be used for an effect.
     // Otherwise, the system's default vibration will be used.
     @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
-    private final FeatureFlags mViewFeatureFlags;
+
+    private float mKeyboardVibrationFixedAmplitude;
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
@@ -65,16 +65,14 @@
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
-        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo),
-                new FeatureFlagsImpl());
+        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
     }
 
     /** @hide */
     @VisibleForTesting HapticFeedbackVibrationProvider(
             Resources res,
             VibratorInfo vibratorInfo,
-            @Nullable SparseArray<VibrationEffect> hapticCustomizations,
-            FeatureFlags viewFeatureFlags) {
+            @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
         mVibratorInfo = vibratorInfo;
         mHapticTextHandleEnabled = res.getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
@@ -83,14 +81,17 @@
             hapticCustomizations = null;
         }
         mHapticCustomizations = hapticCustomizations;
-        mViewFeatureFlags = viewFeatureFlags;
-
         mSafeModeEnabledVibrationEffect =
                 effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
                         ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
                         : VibrationSettings.createEffectFromResource(
                                 res,
                                 com.android.internal.R.array.config_safeModeEnabledVibePattern);
+        mKeyboardVibrationFixedAmplitude = res.getFloat(
+                com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
+        if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
+            mKeyboardVibrationFixedAmplitude = -1;
+        }
     }
 
     /**
@@ -120,6 +121,9 @@
                 return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
 
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
+            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+                return getKeyboardVibration(effectId);
+
             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
             case HapticFeedbackConstants.ENTRY_BUMP:
             case HapticFeedbackConstants.DRAG_CROSSING:
@@ -128,7 +132,6 @@
                         VibrationEffect.EFFECT_TICK,
                         /* fallbackForPredefinedEffect= */ false);
 
-            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
             case HapticFeedbackConstants.VIRTUAL_KEY:
             case HapticFeedbackConstants.EDGE_RELEASE:
             case HapticFeedbackConstants.CALENDAR_DATE:
@@ -204,6 +207,10 @@
             case HapticFeedbackConstants.SCROLL_LIMIT:
                 attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
                 break;
+            case HapticFeedbackConstants.KEYBOARD_TAP:
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+                attrs = createKeyboardVibrationAttributes();
+                break;
             default:
                 attrs = TOUCH_VIBRATION_ATTRIBUTES;
         }
@@ -212,9 +219,12 @@
         if (bypassVibrationIntensitySetting) {
             flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
         }
-        if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) {
+        if (shouldBypassInterruptionPolicy(effectId)) {
             flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
         }
+        if (shouldBypassIntensityScale(effectId)) {
+            flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+        }
 
         return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
     }
@@ -295,6 +305,64 @@
         return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
     }
 
+    private VibrationEffect getKeyboardVibration(int effectId) {
+        if (effectHasCustomization(effectId)) {
+            return mHapticCustomizations.get(effectId);
+        }
+
+        int primitiveId;
+        int predefinedEffectId;
+        boolean predefinedEffectFallback;
+
+        switch (effectId) {
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+                primitiveId = VibrationEffect.Composition.PRIMITIVE_TICK;
+                predefinedEffectId = VibrationEffect.EFFECT_TICK;
+                predefinedEffectFallback = false;
+                break;
+            case HapticFeedbackConstants.KEYBOARD_TAP:
+            default:
+                primitiveId = VibrationEffect.Composition.PRIMITIVE_CLICK;
+                predefinedEffectId = VibrationEffect.EFFECT_CLICK;
+                predefinedEffectFallback = true;
+        }
+        if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) {
+            if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
+                return VibrationEffect.startComposition()
+                        .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude)
+                        .compose();
+            }
+        }
+        return getVibration(effectId, predefinedEffectId,
+                /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
+    }
+
+    private boolean shouldBypassIntensityScale(int effectId) {
+        if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) {
+            // shouldn't bypass if not support keyboard category or no fixed amplitude
+            return false;
+        }
+        switch (effectId) {
+            case HapticFeedbackConstants.KEYBOARD_TAP:
+                return mVibratorInfo.isPrimitiveSupported(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK);
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+                return mVibratorInfo.isPrimitiveSupported(
+                        VibrationEffect.Composition.PRIMITIVE_TICK);
+        }
+        return false;
+    }
+
+    private static VibrationAttributes createKeyboardVibrationAttributes() {
+        if (!Flags.keyboardCategoryEnabled()) {
+            return TOUCH_VIBRATION_ATTRIBUTES;
+        }
+
+        return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
+                .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                .build();
+    }
+
     @Nullable
     private static SparseArray<VibrationEffect> loadHapticCustomizations(
             Resources res, VibratorInfo vibratorInfo) {
@@ -306,8 +374,7 @@
         }
     }
 
-    private static boolean shouldBypassInterruptionPolicy(
-            int effectId, FeatureFlags viewFeatureFlags) {
+    private static boolean shouldBypassInterruptionPolicy(int effectId) {
         switch (effectId) {
             case HapticFeedbackConstants.SCROLL_TICK:
             case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
@@ -315,7 +382,7 @@
                 // The SCROLL_* constants should bypass interruption filter, so that scroll haptics
                 // can play regardless of focus modes like DND. Guard this behavior by the feature
                 // flag controlling the general scroll feedback APIs.
-                return viewFeatureFlags.scrollFeedbackApi();
+                return android.view.flags.Flags.scrollFeedbackApi();
             default:
                 return false;
         }
diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS
index 9afa682..da5a476 100644
--- a/services/core/java/com/android/server/vibrator/OWNERS
+++ b/services/core/java/com/android/server/vibrator/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 345036
-
+khalilahmad@google.com
 lsandrade@google.com
 michaelwr@google.com
-sbowden@google.com
-khalilahmad@google.com
\ No newline at end of file
+roosa@google.com
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index db8a9ae..7f55836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
 import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
 import static android.os.VibrationAttributes.USAGE_ALARM;
 import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
@@ -52,6 +53,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.VibrationIntensity;
+import android.os.vibrator.Flags;
 import android.os.vibrator.VibrationConfig;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
@@ -188,6 +190,8 @@
     @GuardedBy("mLock")
     private boolean mVibrateOn;
     @GuardedBy("mLock")
+    private boolean mKeyboardVibrationOn;
+    @GuardedBy("mLock")
     private int mRingerMode;
     @GuardedBy("mLock")
     private boolean mOnWirelessCharger;
@@ -295,6 +299,8 @@
                 Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY));
         registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
+        registerSettingsObserver(
+                Settings.System.getUriFor(Settings.System.KEYBOARD_VIBRATION_ENABLED));
 
         if (mVibrationConfig.ignoreVibrationsOnWirelessCharger()) {
             Intent batteryStatus = mContext.registerReceiver(
@@ -418,14 +424,9 @@
             }
 
             if (!callerInfo.attrs.isFlagSet(
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
-                if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
-                    return Vibration.Status.IGNORED_FOR_SETTINGS;
-                }
-
-                if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) {
-                    return Vibration.Status.IGNORED_FOR_SETTINGS;
-                }
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                    && !shouldVibrateForUserSetting(callerInfo)) {
+                return Vibration.Status.IGNORED_FOR_SETTINGS;
             }
 
             if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
@@ -497,6 +498,30 @@
         return mRingerMode != AudioManager.RINGER_MODE_SILENT;
     }
 
+    /**
+     * Return {@code true} if the device should vibrate for user setting, and
+     * {@code false} to ignore the vibration.
+     */
+    @GuardedBy("mLock")
+    private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) {
+        final int usage = callerInfo.attrs.getUsage();
+        if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
+            // Main setting disabled.
+            return false;
+        }
+
+        if (Flags.keyboardCategoryEnabled()) {
+            int category = callerInfo.attrs.getCategory();
+            if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
+                // Keyboard touch has a different user setting.
+                return mKeyboardVibrationOn;
+            }
+        }
+
+        // Apply individual user setting based on usage.
+        return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF;
+    }
+
     /** Update all cached settings and triggers registered listeners. */
     void update() {
         updateSettings();
@@ -508,6 +533,8 @@
         synchronized (mLock) {
             mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
             mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
+            mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED,
+                    mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0;
 
             int alarmIntensity = toIntensity(
                     loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
@@ -806,18 +833,24 @@
         private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
 
         public boolean isUidForeground(int uid) {
-            return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            synchronized (this) {
+                return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                        <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            }
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            mProcStatesCache.delete(uid);
+            synchronized (this) {
+                mProcStatesCache.delete(uid);
+            }
         }
 
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-            mProcStatesCache.put(uid, procState);
+            synchronized (this) {
+                mProcStatesCache.put(uid, procState);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 45bd152..ace7777 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -97,7 +97,8 @@
             new VibrationAttributes.Builder().build();
     private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
             VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
-                    | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+                    | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+                    | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
 
     /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
     private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
@@ -771,8 +772,11 @@
     private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            // Scale effect before dispatching it to the input devices or the vibration thread.
-            vib.scaleEffects(mVibrationScaler::scale);
+            if (!vib.callerInfo.attrs.isFlagSet(
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+                // Scale effect before dispatching it to the input devices or the vibration thread.
+                vib.scaleEffects(mVibrationScaler::scale);
+            }
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                     vib.callerInfo, vib.getEffectToPlay());
             if (inputDevicesAvailable) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index c54e3bd..5f8bbe5 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -29,7 +29,6 @@
 
 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;
@@ -38,7 +37,6 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.FileUtils;
-import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -77,8 +75,6 @@
     private final WallpaperCropper mWallpaperCropper;
     private final Context mContext;
 
-    private final boolean mIsLockscreenLiveWallpaperEnabled;
-
     WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper,
             WallpaperCropper wallpaperCropper) {
         mContext = context;
@@ -86,8 +82,6 @@
         mWallpaperCropper = wallpaperCropper;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
-        mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
     }
 
     private JournaledFile makeJournaledFile(int userId) {
@@ -127,42 +121,26 @@
     }
 
     /**
-     * TODO(b/197814683) adapt comment once flag is removed
-     *
      * Load the system wallpaper (and the lock wallpaper, if it exists) from disk
      * @param userId the id of the user for which the wallpaper should be loaded
      * @param keepDimensionHints if false, parse and set the
      *                      {@link DisplayData} width and height for the specified userId
-     * @param wallpaper the wallpaper object to reuse to do the modifications.
-     *                      If null, a new object will be created.
-     * @param lockWallpaper the lock wallpaper object to reuse to do the modifications.
-     *                      If null, a new object will be created.
-     * @param which The wallpaper(s) to load. Only has effect if
-     *                      {@link WallpaperManager#isLockscreenLiveWallpaperEnabled} is true,
-     *                      otherwise both wallpaper will always be loaded.
+     * @param migrateFromOld whether the current wallpaper is pre-N and needs migration
+     * @param which The wallpaper(s) to load.
      * @return a {@link WallpaperLoadingResult} object containing the wallpaper data.
-     *                      This object will contain the {@code wallpaper} and
-     *                      {@code lockWallpaper} provided as parameters, if they are not null.
      */
     public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
-            WallpaperData wallpaper, WallpaperData lockWallpaper, @SetWallpaperFlags int which) {
+            boolean migrateFromOld, @SetWallpaperFlags int which) {
         JournaledFile journal = makeJournaledFile(userId);
         FileInputStream stream = null;
         File file = journal.chooseForRead();
 
-        boolean migrateFromOld = wallpaper == null;
+        boolean loadSystem = (which & FLAG_SYSTEM) != 0;
+        boolean loadLock = (which & FLAG_LOCK) != 0;
+        WallpaperData wallpaper = null;
+        WallpaperData lockWallpaper = null;
 
-        boolean separateLockscreenEngine = mIsLockscreenLiveWallpaperEnabled;
-        boolean loadSystem = !separateLockscreenEngine || (which & FLAG_SYSTEM) != 0;
-        boolean loadLock = !separateLockscreenEngine || (which & FLAG_LOCK) != 0;
-
-        // don't reuse the wallpaper objects in the new version
-        if (separateLockscreenEngine) {
-            wallpaper = null;
-            lockWallpaper = null;
-        }
-
-        if (wallpaper == null && loadSystem) {
+        if (loadSystem) {
             // Do this once per boot
             if (migrateFromOld) migrateFromOld();
             wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
@@ -188,11 +166,8 @@
                 type = parser.next();
                 if (type == XmlPullParser.START_TAG) {
                     String tag = parser.getName();
-                    if (("wp".equals(tag) && loadSystem)
-                            || ("kwp".equals(tag) && mIsLockscreenLiveWallpaperEnabled
-                                && loadLock)) {
-
-                        if ("kwp".equals(tag) && lockWallpaper == null) {
+                    if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) {
+                        if ("kwp".equals(tag)) {
                             lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
                         }
                         WallpaperData wallpaperToParse =
@@ -219,12 +194,6 @@
                             Slog.v(TAG, "mNextWallpaperComponent:"
                                     + wallpaper.nextWallpaperComponent);
                         }
-                    } else if ("kwp".equals(tag) && !mIsLockscreenLiveWallpaperEnabled) {
-                        // keyguard-specific wallpaper for this user (legacy code)
-                        if (lockWallpaper == null) {
-                            lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
-                        }
-                        parseWallpaperAttributes(parser, lockWallpaper, false);
                     }
                 }
             } while (type != XmlPullParser.END_DOCUMENT);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 01ea33f..bdcde66 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -51,6 +51,7 @@
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.PendingIntent;
+import android.app.UidObserver;
 import android.app.UserSwitchObserver;
 import android.app.WallpaperColors;
 import android.app.WallpaperInfo;
@@ -103,6 +104,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -120,6 +122,7 @@
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -185,8 +188,6 @@
     }
 
     private final Object mLock = new Object();
-    /** True to enable a second engine for lock screen wallpaper when different from system wp. */
-    private final boolean mIsLockscreenLiveWallpaperEnabled;
     /** 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. */
@@ -227,7 +228,7 @@
             mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
         }
 
-        WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+        WallpaperData dataForEvent(boolean lockChanged) {
             WallpaperData wallpaper = null;
             synchronized (mLock) {
                 if (lockChanged) {
@@ -249,7 +250,7 @@
             final File changedFile = new File(mWallpaperDir, path);
             final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
             final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
-            final WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
+            final WallpaperData wallpaper = dataForEvent(lockWallpaperChanged);
 
             final boolean moved = (event == MOVED_TO);
             final boolean written = (event == CLOSE_WRITE || moved);
@@ -375,7 +376,7 @@
                 }
 
                 saveSettingsLocked(wallpaper.userId);
-                if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+                if (localSync != null) {
                     localSync.complete();
                 }
             }
@@ -386,129 +387,9 @@
             }
         }
 
-        // Handles static wallpaper changes generated by WallpaperObserver events when
-        // enableSeparateLockScreenEngine() is false.
-        // TODO(b/266818039) Remove this method
-        private void updateWallpapersLegacy(int event, String path) {
-            final boolean moved = (event == MOVED_TO);
-            final boolean written = (event == CLOSE_WRITE || moved);
-            final File changedFile = new File(mWallpaperDir, path);
-
-            // System and system+lock changes happen on the system wallpaper input file;
-            // lock-only changes happen on the dedicated lock wallpaper input file
-            final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
-            final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
-            int notifyColorsWhich = 0;
-            WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
-
-            if (DEBUG) {
-                Slog.v(TAG, "Wallpaper file change: evt=" + event
-                        + " path=" + path
-                        + " sys=" + sysWallpaperChanged
-                        + " lock=" + lockWallpaperChanged
-                        + " imagePending=" + wallpaper.imageWallpaperPending
-                        + " mWhich=0x" + Integer.toHexString(wallpaper.mWhich)
-                        + " written=" + written);
-            }
-
-            if (moved && lockWallpaperChanged) {
-                // We just migrated sys -> lock to preserve imagery for an impending
-                // new system-only wallpaper.  Tell keyguard about it and make sure it
-                // has the right SELinux label.
-                if (DEBUG) {
-                    Slog.i(TAG, "Sys -> lock MOVED_TO");
-                }
-                SELinux.restorecon(changedFile);
-                notifyLockWallpaperChanged();
-                notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
-                return;
-            }
-
-            synchronized (mLock) {
-                if (sysWallpaperChanged || lockWallpaperChanged) {
-                    notifyCallbacksLocked(wallpaper);
-                    if (wallpaper.wallpaperComponent == null
-                            || event != CLOSE_WRITE // includes the MOVED_TO case
-                            || wallpaper.imageWallpaperPending) {
-                        if (written) {
-                            // The image source has finished writing the source image,
-                            // so we now produce the crop rect (in the background), and
-                            // only publish the new displayable (sub)image as a result
-                            // of that work.
-                            if (DEBUG) {
-                                Slog.v(TAG, "Wallpaper written; generating crop");
-                            }
-                            SELinux.restorecon(changedFile);
-                            if (moved) {
-                                // This is a restore, so generate the crop using any just-restored new
-                                // crop guidelines, making sure to preserve our local dimension hints.
-                                // We also make sure to reapply the correct SELinux label.
-                                if (DEBUG) {
-                                    Slog.v(TAG, "moved-to, therefore restore; reloading metadata");
-                                }
-                                loadSettingsLocked(wallpaper.userId, true, FLAG_SYSTEM | FLAG_LOCK);
-                            }
-                            mWallpaperCropper.generateCrop(wallpaper);
-                            if (DEBUG) {
-                                Slog.v(TAG, "Crop done; invoking completion callback");
-                            }
-                            wallpaper.imageWallpaperPending = false;
-                            if (sysWallpaperChanged) {
-                                IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
-                                    @Override
-                                    public void sendResult(Bundle data) throws RemoteException {
-                                        Slog.d(TAG, "publish system wallpaper changed!");
-                                        notifyWallpaperChanged(wallpaper);
-                                    }
-                                };
-                                // If this was the system wallpaper, rebind...
-                                bindWallpaperComponentLocked(mImageWallpaper, true,
-                                        false, wallpaper, callback);
-                                notifyColorsWhich |= FLAG_SYSTEM;
-                            }
-                            if (lockWallpaperChanged
-                                    || (wallpaper.mWhich & FLAG_LOCK) != 0) {
-                                if (DEBUG) {
-                                    Slog.i(TAG, "Lock-relevant wallpaper changed");
-                                }
-                                // either a lock-only wallpaper commit or a system+lock event.
-                                // if it's system-plus-lock we need to wipe the lock bookkeeping;
-                                // we're falling back to displaying the system wallpaper there.
-                                if (!lockWallpaperChanged) {
-                                    mLockWallpaperMap.remove(wallpaper.userId);
-                                }
-                                // and in any case, tell keyguard about it
-                                notifyLockWallpaperChanged();
-                                notifyColorsWhich |= FLAG_LOCK;
-                            }
-
-                            saveSettingsLocked(wallpaper.userId);
-                            // Notify the client immediately if only lockscreen wallpaper changed.
-                            if (lockWallpaperChanged && !sysWallpaperChanged) {
-                                notifyWallpaperChanged(wallpaper);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // Outside of the lock since it will synchronize itself
-            if (notifyColorsWhich != 0) {
-                notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
-            }
-        }
-
         @Override
         public void onEvent(int event, String path) {
-            if (path == null) {
-                return;
-            }
-
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                updateWallpapers(event, path);
-            } else {
-                updateWallpapersLegacy(event, path);
-            }
+            if (path != null) updateWallpapers(event, path);
         }
     }
 
@@ -525,17 +406,6 @@
         }
     }
 
-    private void notifyLockWallpaperChanged() {
-        final IWallpaperManagerCallback cb = mKeyguardListener;
-        if (cb != null) {
-            try {
-                cb.onWallpaperChanged();
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to notify keyguard callback about wallpaper changes", e);
-            }
-        }
-    }
-
     void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
         if (DEBUG) {
             Slog.i(TAG, "Notifying wallpaper colors changed");
@@ -594,14 +464,12 @@
 
     private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
             int userId, int displayId) {
-        final IWallpaperManagerCallback keyguardListener;
         final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>();
         synchronized (mLock) {
             final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
                     getWallpaperCallbacks(userId, displayId);
             final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
                     getWallpaperCallbacks(UserHandle.USER_ALL, displayId);
-            keyguardListener = mKeyguardListener;
 
             if (currentUserColorListeners != null) {
                 final int count = currentUserColorListeners.beginBroadcast();
@@ -630,15 +498,6 @@
                 Slog.w(TAG, "onWallpaperColorsChanged() threw an exception", e);
             }
         }
-
-        // Only shows Keyguard on default display
-        if (keyguardListener != null && displayId == DEFAULT_DISPLAY) {
-            try {
-                keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "keyguardListener.onWallpaperColorsChanged threw an exception", e);
-            }
-        }
     }
 
     /**
@@ -759,8 +618,6 @@
     private final MyPackageMonitor mMonitor;
     private final AppOpsManager mAppOpsManager;
 
-    // TODO("b/264637309") probably move this in WallpaperDisplayUtils,
-    //  after logic is changed for the lockscreen lwp project
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
 
@@ -811,8 +668,6 @@
     protected WallpaperData mLastWallpaper;
     // The currently bound lock screen only wallpaper, or null if none
     protected WallpaperData mLastLockWallpaper;
-    private IWallpaperManagerCallback mKeyguardListener;
-    private boolean mWaitingForUnlock;
 
     /**
      * Flag set to true after reboot if the home wallpaper is waiting for the device to be unlocked.
@@ -1014,8 +869,8 @@
                 if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
                     Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
                             + ", reverting to built-in wallpaper!");
-                    int which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
-                    clearWallpaperLocked(which, mWallpaper.userId, null);
+                    int which = mWallpaper.mWhich;
+                    clearWallpaperLocked(which, mWallpaper.userId, false, null);
                 }
             }
         };
@@ -1195,7 +1050,7 @@
                 } else {
                     // Timeout
                     Slog.w(TAG, "Reverting to built-in wallpaper!");
-                    clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, null);
+                    clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
                     final String flattened = wpService.flattenToString();
                     EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
                             flattened.substring(0, Math.min(flattened.length(),
@@ -1235,7 +1090,7 @@
                                 if (mLmkLimitRebindRetries <= 0) {
                                     Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!");
                                     clearWallpaperLocked(
-                                            mWallpaper.mWhich, mWallpaper.userId, null);
+                                            mWallpaper.mWhich, mWallpaper.userId, false, null);
                                     mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
                                     return;
                                 }
@@ -1254,7 +1109,7 @@
                                     && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
                                     > SystemClock.uptimeMillis()) {
                                 Slog.w(TAG, "Reverting to built-in wallpaper!");
-                                clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null);
+                                clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, false, null);
                             } else {
                                 mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
                                 tryToRebind();
@@ -1291,19 +1146,8 @@
                 if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
                     return;
                 }
-
-                // Live wallpapers always are system wallpapers unless lock screen live wp is
-                // enabled.
-                which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
+                which = mWallpaper.mWhich;
                 mWallpaper.primaryColors = primaryColors;
-
-                // It's also the lock screen wallpaper when we don't have a bitmap in there.
-                if (displayId == DEFAULT_DISPLAY) {
-                    final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
-                    if (lockedWallpaper == null) {
-                        which |= FLAG_LOCK;
-                    }
-                }
             }
             if (which != 0) {
                 notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId);
@@ -1489,9 +1333,7 @@
                                 wallpaper, null)) {
                             Slog.w(TAG, "Wallpaper " + wpService
                                     + " no longer available; reverting to default");
-                            int which = mIsLockscreenLiveWallpaperEnabled
-                                    ? wallpaper.mWhich : FLAG_SYSTEM;
-                            clearWallpaperLocked(which, wallpaper.userId, null);
+                            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                         }
                     }
                 }
@@ -1565,7 +1407,6 @@
 
         boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
             boolean changed = false;
-            int which = mIsLockscreenLiveWallpaperEnabled ? wallpaper.mWhich : FLAG_SYSTEM;
             if (wallpaper.wallpaperComponent != null) {
                 int change = isPackageDisappearing(wallpaper.wallpaperComponent
                         .getPackageName());
@@ -1575,7 +1416,7 @@
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
                                 + wallpaper.wallpaperComponent);
-                        clearWallpaperLocked(which, wallpaper.userId, null);
+                        clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                     }
                 }
             }
@@ -1596,7 +1437,7 @@
                 } catch (NameNotFoundException e) {
                     Slog.w(TAG, "Wallpaper component gone, removing: "
                             + wallpaper.wallpaperComponent);
-                    clearWallpaperLocked(which, wallpaper.userId, null);
+                    clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                 }
             }
             if (wallpaper.nextWallpaperComponent != null
@@ -1645,13 +1486,44 @@
         mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
         mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
+
+        if (mContext.getResources().getBoolean(
+                R.bool.config_pauseWallpaperRenderWhenStateChangeEnabled)) {
+            // Pause wallpaper rendering engine as soon as a performance impacted app is launched.
+            final String[] pauseRenderList = mContext.getResources().getStringArray(
+                    R.array.pause_wallpaper_render_when_state_change);
+            final IntArray pauseRenderUids = new IntArray();
+            for (String pauseRenderApp : pauseRenderList) {
+                try {
+                    int uid = mContext.getPackageManager().getApplicationInfo(
+                            pauseRenderApp, 0).uid;
+                    pauseRenderUids.add(uid);
+                } catch (Exception e) {
+                    Slog.e(TAG, e.toString());
+                }
+            }
+            if (pauseRenderUids.size() > 0) {
+                try {
+                    ActivityManager.getService().registerUidObserverForUids(new UidObserver() {
+                        @Override
+                        public void onUidStateChanged(int uid, int procState, long procStateSeq,
+                                int capability) {
+                            pauseOrResumeRenderingImmediately(
+                                    procState == ActivityManager.PROCESS_STATE_TOP);
+                        }
+                    }, ActivityManager.UID_OBSERVER_PROCSTATE,
+                            ActivityManager.PROCESS_STATE_TOP, "android",
+                            pauseRenderUids.toArray());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, e.toString());
+                }
+            }
+        }
+
         mMonitor = new MyPackageMonitor();
         mColorsChangedListeners = new SparseArray<>();
         mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
                 mWallpaperCropper);
-
-        mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
         mIsMultiCropEnabled =
                 SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
@@ -1718,8 +1590,7 @@
                 if (DEBUG) {
                     Slog.i(TAG, "Unable to regenerate crop; resetting");
                 }
-                int which = isLockscreenLiveWallpaperEnabled() ? wallpaper.mWhich : FLAG_SYSTEM;
-                clearWallpaperLocked(which, UserHandle.USER_SYSTEM, null);
+                clearWallpaperLocked(wallpaper.mWhich, UserHandle.USER_SYSTEM, false, null);
             }
         } else {
             if (DEBUG) {
@@ -1846,29 +1717,19 @@
     public void onUnlockUser(final int userId) {
         synchronized (mLock) {
             if (mCurrentUserId == userId) {
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    if (mHomeWallpaperWaitingForUnlock) {
-                        final WallpaperData systemWallpaper =
-                                getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-                        switchWallpaper(systemWallpaper, null);
-                        // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
-                        notifyCallbacksLocked(systemWallpaper);
-                    }
-                    if (mLockWallpaperWaitingForUnlock) {
-                        final WallpaperData lockWallpaper =
-                                getWallpaperSafeLocked(userId, FLAG_LOCK);
-                        switchWallpaper(lockWallpaper, null);
-                        notifyCallbacksLocked(lockWallpaper);
-                    }
-                }
-
-                if (mWaitingForUnlock && !mIsLockscreenLiveWallpaperEnabled) {
-                    // the desired wallpaper is not direct-boot aware, load it now
+                if (mHomeWallpaperWaitingForUnlock) {
                     final WallpaperData systemWallpaper =
                             getWallpaperSafeLocked(userId, FLAG_SYSTEM);
                     switchWallpaper(systemWallpaper, null);
+                    // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
                     notifyCallbacksLocked(systemWallpaper);
                 }
+                if (mLockWallpaperWaitingForUnlock) {
+                    final WallpaperData lockWallpaper =
+                            getWallpaperSafeLocked(userId, FLAG_LOCK);
+                    switchWallpaper(lockWallpaper, null);
+                    notifyCallbacksLocked(lockWallpaper);
+                }
 
                 // Make sure that the SELinux labeling of all the relevant files is correct.
                 // This corrects for mislabeling bugs that might have arisen from move-to
@@ -1917,21 +1778,15 @@
                 }
                 mCurrentUserId = userId;
                 systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
-                            ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
-                } else {
-                    final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId);
-                    lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper;
-                }
+                lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
+                        ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
 
                 // Not started watching yet, in case wallpaper data was loaded for other reasons.
                 if (systemWallpaper.wallpaperObserver == null) {
                     systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
                     systemWallpaper.wallpaperObserver.startWatching();
                 }
-                if (mIsLockscreenLiveWallpaperEnabled && lockWallpaper != systemWallpaper)  {
+                if (lockWallpaper != systemWallpaper)  {
                     switchWallpaper(lockWallpaper, null);
                 }
                 switchWallpaper(systemWallpaper, reply);
@@ -1951,11 +1806,8 @@
 
     void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
         synchronized (mLock) {
-            mWaitingForUnlock = false;
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
-                if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
-            }
+            if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
+            if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
 
             final ComponentName cname = wallpaper.wallpaperComponent != null ?
                     wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
@@ -1969,37 +1821,19 @@
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
                 }
-
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    onSwitchWallpaperFailLocked(wallpaper, reply, si);
-                    return;
-                }
-
-                if (si == null) {
-                    clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, reply);
-                } else {
-                    Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
-                    // We might end up persisting the current wallpaper data
-                    // while locked, so pretend like the component was actually
-                    // bound into place
-                    wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
-                    final WallpaperData fallback = new WallpaperData(wallpaper.userId, FLAG_LOCK);
-                    bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
-                    mWaitingForUnlock = true;
-                }
+                onSwitchWallpaperFailLocked(wallpaper, reply, si);
             }
         }
     }
 
     /**
      * Fallback method if a wallpaper fails to load on boot or after a user switch.
-     * Only called if mIsLockscreenLiveWallpaperEnabled is true.
      */
     private void onSwitchWallpaperFailLocked(
             WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
 
         if (serviceInfo == null) {
-            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, reply);
             return;
         }
         Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2030,12 +1864,8 @@
 
         WallpaperData data = null;
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                boolean fromForeground = isFromForegroundApp(callingPackage);
-                clearWallpaperLocked(which, userId, fromForeground, null);
-            } else {
-                clearWallpaperLocked(which, userId, null);
-            }
+            boolean fromForeground = isFromForegroundApp(callingPackage);
+            clearWallpaperLocked(which, userId, fromForeground, null);
 
             if (which == FLAG_LOCK) {
                 data = mLockWallpaperMap.get(userId);
@@ -2116,91 +1946,6 @@
         }
     }
 
-    // TODO(b/266818039) remove
-    private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
-
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            clearWallpaperLocked(which, userId, false, reply);
-            return;
-        }
-
-        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
-            throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
-        }
-
-        WallpaperData wallpaper = null;
-        if (which == FLAG_LOCK) {
-            wallpaper = mLockWallpaperMap.get(userId);
-            if (wallpaper == null) {
-                // It's already gone; we're done.
-                if (DEBUG) {
-                    Slog.i(TAG, "Lock wallpaper already cleared");
-                }
-                return;
-            }
-        } else {
-            wallpaper = mWallpaperMap.get(userId);
-            if (wallpaper == null) {
-                // Might need to bring it in the first time to establish our rewrite
-                loadSettingsLocked(userId, false, FLAG_SYSTEM);
-                wallpaper = mWallpaperMap.get(userId);
-            }
-        }
-        if (wallpaper == null) {
-            return;
-        }
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            if (clearWallpaperBitmaps(wallpaper)) {
-                if (which == FLAG_LOCK) {
-                    mLockWallpaperMap.remove(userId);
-                    final IWallpaperManagerCallback cb = mKeyguardListener;
-                    if (cb != null) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Notifying keyguard of lock wallpaper clear");
-                        }
-                        try {
-                            cb.onWallpaperChanged();
-                        } catch (RemoteException e) {
-                            Slog.w(TAG, "Failed to notify keyguard after wallpaper clear", e);
-                        }
-                    }
-                    saveSettingsLocked(userId);
-                    return;
-                }
-            }
-
-            RuntimeException e = null;
-            try {
-                wallpaper.primaryColors = null;
-                wallpaper.imageWallpaperPending = false;
-                if (userId != mCurrentUserId) return;
-                if (bindWallpaperComponentLocked(null, true, false, wallpaper, reply)) {
-                    return;
-                }
-            } catch (IllegalArgumentException e1) {
-                e = e1;
-            }
-
-            // This can happen if the default wallpaper component doesn't
-            // exist.  This should be a system configuration problem, but
-            // let's not let it crash the system and just live with no
-            // wallpaper.
-            Slog.e(TAG, "Default wallpaper component not found!", e);
-            clearWallpaperComponentLocked(wallpaper);
-            if (reply != null) {
-                try {
-                    reply.sendResult(null);
-                } catch (RemoteException e1) {
-                    Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
     private boolean hasCrossUserPermission() {
         final int interactPermission =
                 mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
@@ -2578,45 +2323,20 @@
      * @param animationDuration Duration of the animation, or 0 when immediate.
      */
     public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            List<IWallpaperEngine> engines = new ArrayList<>();
-            synchronized (mLock) {
-                mInAmbientMode = inAmbientMode;
-                for (WallpaperData data : getActiveWallpapers()) {
-                    if (data.connection.mInfo == null
-                            || data.connection.mInfo.supportsAmbientMode()) {
-                        // TODO(multi-display) Extends this method with specific display.
-                        IWallpaperEngine engine = data.connection
-                                .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
-                        if (engine != null) engines.add(engine);
-                    }
-                }
-            }
-            for (IWallpaperEngine engine : engines) {
-                try {
-                    engine.setInAmbientMode(inAmbientMode, animationDuration);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to set ambient mode", e);
-                }
-            }
-            return;
-        }
-
-        final IWallpaperEngine engine;
+        List<IWallpaperEngine> engines = new ArrayList<>();
         synchronized (mLock) {
             mInAmbientMode = inAmbientMode;
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            // The wallpaper info is null for image wallpaper, also use the engine in this case.
-            if (data != null && data.connection != null && (data.connection.mInfo == null
-                    || data.connection.mInfo.supportsAmbientMode())) {
-                // TODO(multi-display) Extends this method with specific display.
-                engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
-            } else {
-                engine = null;
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.mInfo == null
+                        || data.connection.mInfo.supportsAmbientMode()) {
+                    // TODO(multi-display) Extends this method with specific display.
+                    IWallpaperEngine engine = data.connection
+                            .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
+                    if (engine != null) engines.add(engine);
+                }
             }
         }
-
-        if (engine != null) {
+        for (IWallpaperEngine engine : engines) {
             try {
                 engine.setInAmbientMode(inAmbientMode, animationDuration);
             } catch (RemoteException e) {
@@ -2625,40 +2345,49 @@
         }
     }
 
+    private void pauseOrResumeRenderingImmediately(boolean pause) {
+        synchronized (mLock) {
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.mInfo == null) {
+                    continue;
+                }
+                if (pause || LocalServices.getService(ActivityTaskManagerInternal.class)
+                        .isUidForeground(data.connection.mInfo.getServiceInfo()
+                                .applicationInfo.uid)) {
+                    if (data.connection.containsDisplay(
+                            mWindowManagerInternal.getTopFocusedDisplayId())) {
+                        data.connection.forEachDisplayConnector(displayConnector -> {
+                            if (displayConnector.mEngine != null) {
+                                try {
+                                    displayConnector.mEngine.setVisibility(!pause);
+                                } catch (RemoteException e) {
+                                    Slog.w(TAG, "Failed to set visibility", e);
+                                }
+                            }
+                        });
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Propagate a wake event to the wallpaper engine(s).
      */
     public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
         checkCallerIsSystemOrSystemUi();
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    data.connection.forEachDisplayConnector(displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                data.connection.forEachDisplayConnector(displayConnector -> {
+                    if (displayConnector.mEngine != null) {
+                        try {
+                            displayConnector.mEngine.dispatchWallpaperCommand(
+                                    WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
                         }
-                    });
-                }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null) {
-                data.connection.forEachDisplayConnector(
-                        displayConnector -> {
-                            if (displayConnector.mEngine != null) {
-                                try {
-                                    displayConnector.mEngine.dispatchWallpaperCommand(
-                                            WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
-                                }
-                            }
-                        });
+                    }
+                });
             }
         }
     }
@@ -2669,36 +2398,18 @@
     public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
         checkCallerIsSystemOrSystemUi();
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    data.connection.forEachDisplayConnector(displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
-                                        extras);
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                data.connection.forEachDisplayConnector(displayConnector -> {
+                    if (displayConnector.mEngine != null) {
+                        try {
+                            displayConnector.mEngine.dispatchWallpaperCommand(
+                                    WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+                                    extras);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
                         }
-                    });
-                }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null) {
-                data.connection.forEachDisplayConnector(
-                        displayConnector -> {
-                            if (displayConnector.mEngine != null) {
-                                try {
-                                    displayConnector.mEngine.dispatchWallpaperCommand(
-                                            WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
-                                            extras);
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
-                                }
-                            }
-                        });
+                    }
+                });
             }
         }
     }
@@ -2708,35 +2419,18 @@
      */
     private void notifyScreenTurnedOn(int displayId) {
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    if (data.connection.containsDisplay(displayId)) {
-                        final IWallpaperEngine engine = data.connection
-                                .getDisplayConnectorOrCreate(displayId).mEngine;
-                        if (engine != null) {
-                            try {
-                                engine.onScreenTurnedOn();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to notify that the screen turned on", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.containsDisplay(displayId)) {
+                    final IWallpaperEngine engine = data.connection
+                            .getDisplayConnectorOrCreate(displayId).mEngine;
+                    if (engine != null) {
+                        try {
+                            engine.onScreenTurnedOn();
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to notify that the screen turned on", e);
                         }
                     }
                 }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null
-                    && data.connection != null
-                    && data.connection.containsDisplay(displayId)) {
-                final IWallpaperEngine engine = data.connection
-                        .getDisplayConnectorOrCreate(displayId).mEngine;
-                if (engine != null) {
-                    try {
-                        engine.onScreenTurnedOn();
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to notify that the screen turned on", e);
-                    }
-                }
             }
         }
     }
@@ -2746,35 +2440,18 @@
      */
     private void notifyScreenTurningOn(int displayId) {
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    if (data.connection.containsDisplay(displayId)) {
-                        final IWallpaperEngine engine = data.connection
-                                .getDisplayConnectorOrCreate(displayId).mEngine;
-                        if (engine != null) {
-                            try {
-                                engine.onScreenTurningOn();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to notify that the screen is turning on", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.containsDisplay(displayId)) {
+                    final IWallpaperEngine engine = data.connection
+                            .getDisplayConnectorOrCreate(displayId).mEngine;
+                    if (engine != null) {
+                        try {
+                            engine.onScreenTurningOn();
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to notify that the screen is turning on", e);
                         }
                     }
                 }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null
-                    && data.connection != null
-                    && data.connection.containsDisplay(displayId)) {
-                final IWallpaperEngine engine = data.connection
-                        .getDisplayConnectorOrCreate(displayId).mEngine;
-                if (engine != null) {
-                    try {
-                        engine.onScreenTurningOn();
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to notify that the screen is turning on", e);
-                    }
-                }
             }
         }
     }
@@ -2784,25 +2461,7 @@
      */
     private void notifyKeyguardGoingAway() {
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    data.connection.forEachDisplayConnector(displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
-                                        -1, -1, -1, new Bundle());
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
-                            }
-                        }
-                    });
-                }
-                return;
-            }
-
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null) {
+            for (WallpaperData data : getActiveWallpapers()) {
                 data.connection.forEachDisplayConnector(displayConnector -> {
                     if (displayConnector.mEngine != null) {
                         try {
@@ -2818,15 +2477,6 @@
         }
     }
 
-    @Override
-    public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
-        checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
-        synchronized (mLock) {
-            mKeyguardListener = cb;
-        }
-        return true;
-    }
-
     private WallpaperData[] getActiveWallpapers() {
         WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
@@ -2838,12 +2488,11 @@
                 : new WallpaperData[0];
     }
 
-    // TODO(b/266818039) remove
     private WallpaperData[] getWallpapers() {
         WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
         boolean systemValid = systemWallpaper != null;
-        boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled();
+        boolean lockValid = lockWallpaper != null;
         return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
                 : systemValid ? new WallpaperData[]{systemWallpaper}
                 : lockValid ? new WallpaperData[]{lockWallpaper}
@@ -2977,54 +2626,29 @@
                     lockWallpaper.mWallpaperDimAmount = maxDimAmount;
                 }
 
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    boolean changed = false;
-                    for (WallpaperData wp : getActiveWallpapers()) {
-                        if (wp != null && wp.connection != null) {
-                            wp.connection.forEachDisplayConnector(connector -> {
-                                if (connector.mEngine != null) {
-                                    try {
-                                        connector.mEngine.applyDimming(maxDimAmount);
-                                    } catch (RemoteException e) {
-                                        Slog.w(TAG, "Can't apply dimming on wallpaper display "
-                                                        + "connector", e);
-                                    }
-                                }
-                            });
-                            // Need to extract colors again to re-calculate dark hints after
-                            // applying dimming.
-                            wp.mIsColorExtractedFromDim = true;
-                            pendingColorExtraction.add(wp);
-                            changed = true;
-                        }
-                    }
-                    if (changed) {
-                        saveSettingsLocked(wallpaper.userId);
-                    }
-                } else {
-                    if (wallpaper.connection != null) {
-                        wallpaper.connection.forEachDisplayConnector(connector -> {
+                boolean changed = false;
+                for (WallpaperData wp : getActiveWallpapers()) {
+                    if (wp != null && wp.connection != null) {
+                        wp.connection.forEachDisplayConnector(connector -> {
                             if (connector.mEngine != null) {
                                 try {
                                     connector.mEngine.applyDimming(maxDimAmount);
                                 } catch (RemoteException e) {
-                                    Slog.w(TAG,
-                                            "Can't apply dimming on wallpaper display connector",
-                                            e);
+                                    Slog.w(TAG, "Can't apply dimming on wallpaper display "
+                                                    + "connector", e);
                                 }
                             }
                         });
                         // Need to extract colors again to re-calculate dark hints after
                         // applying dimming.
-                        wallpaper.mIsColorExtractedFromDim = true;
-                        notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
-                        if (lockWallpaper != null) {
-                            lockWallpaper.mIsColorExtractedFromDim = true;
-                            notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
-                        }
-                        saveSettingsLocked(wallpaper.userId);
+                        wp.mIsColorExtractedFromDim = true;
+                        pendingColorExtraction.add(wp);
+                        changed = true;
                     }
                 }
+                if (changed) {
+                    saveSettingsLocked(wallpaper.userId);
+                }
             }
             for (WallpaperData wp: pendingColorExtraction) {
                 notifyWallpaperColorsChanged(wp, wp.mWhich);
@@ -3180,10 +2804,7 @@
             if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) {
                 Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
                         + " updating system wallpaper");
-                if (!migrateStaticSystemToLockWallpaperLocked(userId)
-                        && !isLockscreenLiveWallpaperEnabled()) {
-                    which |= FLAG_LOCK;
-                }
+                migrateStaticSystemToLockWallpaperLocked(userId);
             }
 
             wallpaper = getWallpaperSafeLocked(userId, which);
@@ -3211,13 +2832,13 @@
         }
     }
 
-    private boolean migrateStaticSystemToLockWallpaperLocked(int userId) {
+    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");
             }
-            return true;
+            return;
         }
 
         // We know a-priori that there is no lock-only wallpaper currently
@@ -3231,25 +2852,21 @@
 
         // Migrate the bitmap files outright; no need to copy
         try {
-            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getWallpaperFile().exists()) {
+            if (sysWP.getWallpaperFile().exists()) {
                 Os.rename(sysWP.getWallpaperFile().getAbsolutePath(),
                         lockWP.getWallpaperFile().getAbsolutePath());
             }
-            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getCropFile().exists()) {
+            if (sysWP.getCropFile().exists()) {
                 Os.rename(sysWP.getCropFile().getAbsolutePath(),
                         lockWP.getCropFile().getAbsolutePath());
             }
             mLockWallpaperMap.put(userId, lockWP);
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                SELinux.restorecon(lockWP.getWallpaperFile());
-                mLastLockWallpaper = lockWP;
-            }
-            return true;
+            SELinux.restorecon(lockWP.getWallpaperFile());
+            mLastLockWallpaper = lockWP;
         } catch (ErrnoException e) {
             // can happen when migrating default wallpaper (which is not stored in wallpaperFile)
             Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
             clearWallpaperBitmaps(lockWP);
-            return false;
         }
     }
 
@@ -3306,13 +2923,8 @@
     @VisibleForTesting
     boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            boolean fromForeground = isFromForegroundApp(callingPackage);
-            return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
-        } else {
-            setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
-            return true;
-        }
+        boolean fromForeground = isFromForegroundApp(callingPackage);
+        return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
     }
 
     private boolean setWallpaperComponentInternal(ComponentName name,  @SetWallpaperFlags int which,
@@ -3425,87 +3037,6 @@
         return bindSuccess;
     }
 
-    // TODO(b/266818039) Remove this method
-    private void setWallpaperComponentInternalLegacy(ComponentName name, String callingPackage,
-            @SetWallpaperFlags int which, int userId) {
-        userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
-                false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
-        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
-
-        int legacyWhich = FLAG_SYSTEM;
-        boolean shouldNotifyColors = false;
-        WallpaperData wallpaper;
-
-        synchronized (mLock) {
-            Slog.v(TAG, "setWallpaperComponentLegacy name=" + name + ", which=" + which);
-            wallpaper = mWallpaperMap.get(userId);
-            if (wallpaper == null) {
-                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
-            }
-            final long ident = Binder.clearCallingIdentity();
-
-            // Live wallpapers can't be specified for keyguard.  If we're using a static
-            // system+lock image currently, migrate the system wallpaper to be a lock-only
-            // image as part of making a different live component active as the system
-            // wallpaper.
-            if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
-                if (mLockWallpaperMap.get(userId) == null) {
-                    // We're using the static imagery and there is no lock-specific image in place,
-                    // therefore it's a shared system+lock image that we need to migrate.
-                    Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
-                            + "updating system wallpaper");
-                    if (!migrateStaticSystemToLockWallpaperLocked(userId)) {
-                        which |= FLAG_LOCK;
-                    }
-                }
-            }
-
-            // New live wallpaper is also a lock wallpaper if nothing is set
-            if (mLockWallpaperMap.get(userId) == null) {
-                legacyWhich |= FLAG_LOCK;
-            }
-
-            try {
-                wallpaper.imageWallpaperPending = false;
-                wallpaper.mWhich = which;
-                wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
-                boolean same = changingToSame(name, wallpaper);
-
-                // force rebind when reapplying a system-only wallpaper to system+lock
-                boolean forceRebind = same && mLockWallpaperMap.get(userId) != null
-                        && which == (FLAG_SYSTEM | FLAG_LOCK);
-                if (bindWallpaperComponentLocked(name, forceRebind, true, wallpaper, null)) {
-                    if (!same) {
-                        wallpaper.primaryColors = null;
-                    } else {
-                        if (wallpaper.connection != null) {
-                            wallpaper.connection.forEachDisplayConnector(displayConnector -> {
-                                try {
-                                    if (displayConnector.mEngine != null) {
-                                        displayConnector.mEngine.dispatchWallpaperCommand(
-                                                COMMAND_REAPPLY, 0, 0, 0, null);
-                                    }
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Error sending apply message to wallpaper", e);
-                                }
-                            });
-                        }
-                    }
-                    wallpaper.wallpaperId = makeWallpaperIdLocked();
-                    notifyCallbacksLocked(wallpaper);
-                    shouldNotifyColors = true;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        if (shouldNotifyColors) {
-            notifyWallpaperColorsChanged(wallpaper, legacyWhich);
-            notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
-        }
-    }
-
     /**
      * Determines if the given component name is the default component. Note: a null name can be
      * used to represent the default component.
@@ -3677,21 +3208,11 @@
                 Slog.w(TAG, msg);
                 return false;
             }
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                maybeDetachLastWallpapers(wallpaper);
-            } else if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null
-                    && !wallpaper.equals(mFallbackWallpaper)) {
-                detachWallpaperLocked(mLastWallpaper);
-            }
+            maybeDetachLastWallpapers(wallpaper);
             wallpaper.wallpaperComponent = componentName;
             wallpaper.connection = newConn;
             newConn.mReply = reply;
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                updateCurrentWallpapers(wallpaper);
-            } else if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(
-                    mFallbackWallpaper)) {
-                mLastWallpaper = wallpaper;
-            }
+            updateCurrentWallpapers(wallpaper);
             updateFallbackConnection();
         } catch (RemoteException e) {
             String msg = "Remote exception for " + componentName + "\n" + e;
@@ -3707,7 +3228,6 @@
     }
 
     // Updates tracking of the currently bound wallpapers.
-    // Assumes isLockscreenLiveWallpaperEnabled is true.
     private void updateCurrentWallpapers(WallpaperData newWallpaper) {
         if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
             return;
@@ -3721,8 +3241,7 @@
         }
     }
 
-    // Detaches previously bound wallpapers if no longer in use. Assumes
-    // isLockscreenLiveWallpaperEnabled is true.
+    // Detaches previously bound wallpapers if no longer in use.
     private void maybeDetachLastWallpapers(WallpaperData newWallpaper) {
         if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
             return;
@@ -3915,11 +3434,6 @@
     }
 
     @Override
-    public boolean isLockscreenLiveWallpaperEnabled() {
-        return mIsLockscreenLiveWallpaperEnabled;
-    }
-
-    @Override
     public boolean isMultiCropEnabled() {
         return mIsMultiCropEnabled;
     }
@@ -3982,7 +3496,9 @@
         if (wallpaper == null) {
             // common case, this is the first lookup post-boot of the system or
             // unified lock, so we bring up the saved state lazily now and recheck.
-            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM;
+            // if we're loading the system wallpaper for the first time, also load the lock
+            // wallpaper to determine if the system wallpaper is system+lock or system only.
+            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK;
             loadSettingsLocked(userId, false, whichLoad);
             wallpaper = whichSet.get(userId);
             if (wallpaper == null) {
@@ -4006,13 +3522,12 @@
 
     private void loadSettingsLocked(int userId, boolean keepDimensionHints, int which) {
         initializeFallbackWallpaper();
-        WallpaperData wallpaperData = mWallpaperMap.get(userId);
-        WallpaperData lockWallpaperData = mLockWallpaperMap.get(userId);
+        boolean restoreFromOld = !mWallpaperMap.contains(userId);
         WallpaperDataParser.WallpaperLoadingResult result = mWallpaperDataParser.loadSettingsLocked(
-                userId, keepDimensionHints, wallpaperData, lockWallpaperData, which);
+                userId, keepDimensionHints, restoreFromOld, which);
 
-        boolean updateSystem = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_SYSTEM) != 0;
-        boolean updateLock = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_LOCK) != 0;
+        boolean updateSystem = (which & FLAG_SYSTEM) != 0;
+        boolean updateLock = (which & FLAG_LOCK) != 0;
 
         if (updateSystem) mWallpaperMap.put(userId, result.getSystemWallpaperData());
         if (updateLock) {
@@ -4175,8 +3690,6 @@
             if (mFallbackWallpaper != null) {
                 dumpWallpaper(mFallbackWallpaper, pw);
             }
-            pw.print("mIsLockscreenLiveWallpaperEnabled=");
-            pw.println(mIsLockscreenLiveWallpaperEnabled);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ed10346..bdab4d4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1316,6 +1316,9 @@
         if (mLaunchIntoPipHostActivity != null) {
             pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);
         }
+        if (mWaitForEnteringPinnedMode) {
+            pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true");
+        }
 
         mLetterboxUiController.dump(pw, prefix);
 
@@ -3052,7 +3055,7 @@
 
     @Override
     boolean providesOrientation() {
-        return mStyleFillsParent;
+        return mStyleFillsParent || mOccludesParent;
     }
 
     @Override
@@ -3132,9 +3135,7 @@
     }
 
     boolean canReceiveKeys() {
-        // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent
-        return getWindowConfiguration().canReceiveKeys()
-                && (task == null || task.getWindowConfiguration().canReceiveKeys());
+        return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
     }
 
     boolean isResizeable() {
@@ -4029,6 +4030,9 @@
         if (mAppStopped) {
             abortAndClearOptionsAnimation();
         }
+        if (mDisplayContent != null) {
+            mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+        }
     }
 
     boolean isFinishing() {
@@ -5367,18 +5371,11 @@
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
                 if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
-                    Slog.e(TAG, "setVisibility=" + visible
-                            + " while transition is not collecting or finishing "
-                            + this + " caller=" + Debug.getCallers(8));
-                    // Force showing the parents because they may be hidden by previous transition.
                     if (visible) {
-                        final Transaction t = getSyncTransaction();
-                        for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent;
-                                p = p.getParent()) {
-                            if (p.mSurfaceControl != null) {
-                                t.show(p.mSurfaceControl);
-                            }
-                        }
+                        mTransitionController.onVisibleWithoutCollectingTransition(this,
+                                Debug.getCallers(1, 1));
+                    } else {
+                        Slog.w(TAG, "Set invisible without transition " + this);
                     }
                 }
             }
@@ -5394,12 +5391,6 @@
         mLastDeferHidingClient = deferHidingClient;
 
         if (!visible) {
-            // If this activity is about to finish/stopped and now becomes invisible, remove it
-            // from the unknownApp list in case the activity does not want to draw anything, which
-            // keep the user waiting for the next transition to start.
-            if (finishing || isState(STOPPED)) {
-                displayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
-            }
             // Because starting window was transferred, this activity may be a trampoline which has
             // been occluded by next activity. If it has added windows, set client visibility
             // immediately to avoid the client getting RELAYOUT_RES_FIRST_TIME from relayout and
@@ -5843,6 +5834,9 @@
                 break;
             case STOPPED:
                 mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+                if (mDisplayContent != null) {
+                    mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+                }
                 break;
             case DESTROYED:
                 if (app != null && (mVisible || mVisibleRequested)) {
@@ -6523,7 +6517,6 @@
         }
         // Reset the last saved PiP snap fraction on app stop.
         mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
-        mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
         if (isClientVisible()) {
             // Though this is usually unlikely to happen, still make sure the client is invisible.
             setClientVisible(false);
@@ -8271,7 +8264,11 @@
 
     private void clearSizeCompatModeAttributes() {
         mInSizeCompatModeForBounds = false;
+        final float lastSizeCompatScale = mSizeCompatScale;
         mSizeCompatScale = 1f;
+        if (mSizeCompatScale != lastSizeCompatScale) {
+            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
+        }
         mSizeCompatBounds = null;
         mCompatDisplayInsets = null;
         mLetterboxUiController.clearInheritedCompatDisplayInsets();
@@ -8279,11 +8276,7 @@
 
     @VisibleForTesting
     void clearSizeCompatMode() {
-        final float lastSizeCompatScale = mSizeCompatScale;
         clearSizeCompatModeAttributes();
-        if (mSizeCompatScale != lastSizeCompatScale) {
-            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
-        }
         // Clear config override in #updateCompatDisplayInsets().
         final int activityType = getActivityType();
         final Configuration overrideConfig = getRequestedOverrideConfiguration();
@@ -9269,13 +9262,6 @@
             Slog.w(TAG, errorMessage);
         }
 
-        // Configuration's equality doesn't consider seq so if only seq number changes in resolved
-        // override configuration. Therefore ConfigurationContainer doesn't change merged override
-        // configuration, but it's used to push configuration changes so explicitly update that.
-        if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) {
-            onMergedOverrideConfigurationChanged();
-        }
-
         // Before PiP animation is done, th windowing mode of the activity is still the previous
         // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode
         // of activity is changed, it is the signal of the last step to update the PiP states.
@@ -9331,8 +9317,9 @@
         final Task rootTask = getRootTask();
         final float minAspectRatio = getMinAspectRatio();
         final TaskFragment organizedTf = getOrganizedTaskFragment();
+        float aspectRatioToApply = desiredAspectRatio;
         if (task == null || rootTask == null
-                || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
+                || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1)
                 // Don't set aspect ratio if we are in VR mode.
                 || isInVrUiMode(getConfiguration())
                 // TODO(b/232898850): Always respect aspect ratio requests.
@@ -9345,30 +9332,30 @@
         final int containingAppHeight = containingAppBounds.height();
         final float containingRatio = computeAspectRatio(containingAppBounds);
 
-        if (desiredAspectRatio < 1) {
-            desiredAspectRatio = containingRatio;
+        if (aspectRatioToApply < 1) {
+            aspectRatioToApply = containingRatio;
         }
 
-        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
-            desiredAspectRatio = maxAspectRatio;
-        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
-            desiredAspectRatio = minAspectRatio;
+        if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) {
+            aspectRatioToApply = maxAspectRatio;
+        } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) {
+            aspectRatioToApply = minAspectRatio;
         }
 
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
 
-        if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+        if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             if (containingAppWidth < containingAppHeight) {
                 // Width is the shorter side, so we use that to figure-out what the max. height
                 // should be given the aspect ratio.
-                activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f);
             } else {
                 // Height is the shorter side, so we use that to figure-out what the max. width
                 // should be given the aspect ratio.
-                activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f);
             }
-        } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+        } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             boolean adjustWidth;
             switch (getRequestedConfigurationOrientation()) {
                 case ORIENTATION_LANDSCAPE:
@@ -9396,9 +9383,9 @@
                     break;
             }
             if (adjustWidth) {
-                activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f);
             } else {
-                activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 01b8bf7..52ab9b8 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -539,7 +539,7 @@
         if (usf != null) {
             mUserSavedFiles.get(userId).remove(code);
             mSavedFilesInOrder.remove(usf);
-            mPersister.removeSnap(code, userId);
+            mPersister.removeSnapshot(code, userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 27315bb..34bf8ed 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -707,7 +707,7 @@
                 }
             }
 
-            int res;
+            int res = START_CANCELED;
             synchronized (mService.mGlobalLock) {
                 final boolean globalConfigWillChange = mRequest.globalConfig != null
                         && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0;
@@ -719,22 +719,20 @@
                         + "will change = %b", globalConfigWillChange);
 
                 final long origId = Binder.clearCallingIdentity();
-
-                res = resolveToHeavyWeightSwitcherIfNeeded();
-                if (res != START_SUCCESS) {
-                    return res;
-                }
-
                 try {
+                    res = resolveToHeavyWeightSwitcherIfNeeded();
+                    if (res != START_SUCCESS) {
+                        return res;
+                    }
+
                     res = executeRequest(mRequest);
                 } finally {
+                    Binder.restoreCallingIdentity(origId);
                     mRequest.logMessage.append(" result code=").append(res);
                     Slog.i(TAG, mRequest.logMessage.toString());
                     mRequest.logMessage.setLength(0);
                 }
 
-                Binder.restoreCallingIdentity(origId);
-
                 if (globalConfigWillChange) {
                     // If the caller also wants to switch to a new configuration, do so now.
                     // This allows a clean switch, as we are waiting for the current activity
@@ -1099,23 +1097,22 @@
                         "shouldAbortBackgroundActivityStart");
                 BackgroundActivityStartController balController =
                         mSupervisor.getBackgroundActivityLaunchController();
-                balCode =
+                BackgroundActivityStartController.BalVerdict balVerdict =
                         balController.checkBackgroundActivityStart(
-                                callingUid,
-                                callingPid,
-                                callingPackage,
-                                realCallingUid,
-                                realCallingPid,
-                                callerApp,
-                                request.originatingPendingIntent,
-                                request.backgroundStartPrivileges,
-                                intent,
-                                checkedOptions);
-                if (balCode != BAL_ALLOW_DEFAULT) {
-                    request.logMessage.append(" (").append(
-                                    BackgroundActivityStartController.balCodeToString(balCode))
-                            .append(")");
-                }
+                            callingUid,
+                            callingPid,
+                            callingPackage,
+                            realCallingUid,
+                            realCallingPid,
+                            callerApp,
+                            request.originatingPendingIntent,
+                            request.backgroundStartPrivileges,
+                            intent,
+                            checkedOptions);
+                balCode = balVerdict.getCode();
+                request.logMessage.append(" (").append(
+                                BackgroundActivityStartController.balCodeToString(balCode))
+                        .append(")");
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0b67321..c021785 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,6 +68,7 @@
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3001,7 +3002,7 @@
                 || mLastResumedActivity == null) {
             return;
         }
-        var userInfo = mUserManager.getUserInfo(mLastResumedActivity.mUserId);
+        var userInfo = getUserManager().getUserInfo(mLastResumedActivity.mUserId);
         if (userInfo == null || !userInfo.isManagedProfile()) {
             return;
         }
@@ -3686,6 +3687,11 @@
                         getTransitionController(), mWindowManager.mSyncEngine)
                 : null;
 
+        if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
+                && transition != null) {
+            transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+        }
+
         final Runnable enterPipRunnable = () -> {
             synchronized (mGlobalLock) {
                 if (r.getParent() == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12e1e2c..777b5cd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2832,17 +2832,22 @@
     static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
         private ActivityRecord mStarting;
         private boolean mIncludeInvisibleAndFinishing;
+        private boolean mIgnoringKeyguard;
 
-        ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
+        ActivityRecord getOpaqueActivity(
+                @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
             mIncludeInvisibleAndFinishing = true;
+            mIgnoringKeyguard = ignoringKeyguard;
             return container.getActivity(this,
                     true /* traverseTopToBottom */, null /* boundary */);
         }
 
-        ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container,
-                @Nullable ActivityRecord starting) {
+        ActivityRecord getVisibleOpaqueActivity(
+                @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting,
+                boolean ignoringKeyguard) {
             mStarting = starting;
             mIncludeInvisibleAndFinishing = false;
+            mIgnoringKeyguard = ignoringKeyguard;
             final ActivityRecord opaque = container.getActivity(this,
                     true /* traverseTopToBottom */, null /* boundary */);
             mStarting = null;
@@ -2851,7 +2856,9 @@
 
         @Override
         public boolean test(ActivityRecord r) {
-            if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) {
+            if (!mIncludeInvisibleAndFinishing && r != mStarting
+                    && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard)
+                    || (!mIgnoringKeyguard && !r.isVisible()))) {
                 // Ignore invisible activities that are not the currently starting activity
                 // (about to be visible).
                 return false;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index b039646..3dc377d 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -22,6 +22,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
@@ -31,7 +32,8 @@
  * Interface that describes an animation and bridges the animation start to the component
  * responsible for running the animation.
  */
-interface AnimationAdapter {
+@VisibleForTesting
+public interface AnimationAdapter {
 
     long STATUS_BAR_TRANSITION_DURATION = 120L;
 
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 4444709..c79a8b6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -378,8 +378,23 @@
                 if (!wc.isSyncFinished(this)) {
                     allFinished = false;
                     Slog.i(TAG, "Unfinished container: " + wc);
+                    wc.forAllActivities(a -> {
+                        if (a.isVisibleRequested()) {
+                            if (a.isRelaunching()) {
+                                Slog.i(TAG, "  " + a + " is relaunching");
+                            }
+                            a.forAllWindows(w -> {
+                                Slog.i(TAG, "  " + w + " " + w.mWinAnimator.drawStateToString());
+                            }, true /* traverseTopToBottom */);
+                        } else if (a.mDisplayContent != null && !a.mDisplayContent
+                                .mUnknownAppVisibilityController.allResolved()) {
+                            Slog.i(TAG, "  UnknownAppVisibility: " + a.mDisplayContent
+                                    .mUnknownAppVisibilityController.getDebugMessage());
+                        }
+                    });
                 }
             }
+
             for (int i = mDependencies.size() - 1; i >= 0; --i) {
                 allFinished = false;
                 Slog.i(TAG, "Unfinished dependency: " + mDependencies.get(i).mSyncId);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4237668..6d59b29 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -276,6 +276,10 @@
                 // activity, we won't close the activity.
                 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
                 removedWindowContainer = window;
+            } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
+                // skip if current activity is translucent
+                backType = BackNavigationInfo.TYPE_CALLBACK;
+                removedWindowContainer = window;
             } else if (prevActivity != null) {
                 if (!isOccluded || prevActivity.canShowWhenLocked()) {
                     // We have another Activity in the same currentTask to go to
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index e97bda2..9b7b8de 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -22,7 +22,6 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
 
-import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -40,7 +39,6 @@
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
 import android.app.BackgroundStartPrivileges;
-import android.app.ComponentOptions;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -73,14 +71,18 @@
 
     private static final String TAG =
             TAG_WITH_CLASS_NAME ? "BackgroundActivityStartController" : TAG_ATM;
-    public static final String VERDICT_ALLOWED = "Activity start allowed";
-    public static final String VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL =
-            "Activity start would be allowed if the sender granted BAL privileges";
 
     private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS;
     private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
+    public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
+            ActivityOptions.makeBasic()
+                    .setPendingIntentBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
+                    .setPendingIntentCreatorBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
 
     private final ActivityTaskManagerService mService;
+
     private final ActivityTaskSupervisor mSupervisor;
 
     // TODO(b/263368846) Rename when ASM logic is moved in
@@ -202,15 +204,181 @@
         return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
                 realCallingUid, realCallingPid,
                 callerApp, originatingPendingIntent,
-                backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
+                backgroundStartPrivileges, intent, checkedOptions).blocks();
+    }
+
+    private class BalState {
+
+        private final String mCallingPackage;
+        private final int mCallingUid;
+        private final int mCallingPid;
+        private final @ActivityTaskManagerService.AppSwitchState int mAppSwitchState;
+        private final boolean mCallingUidHasAnyVisibleWindow;
+        private final @ActivityManager.ProcessState int mCallingUidProcState;
+        private final boolean mIsCallingUidPersistentSystemProcess;
+        private final BackgroundStartPrivileges mBalAllowedByPiSender;
+        private final BackgroundStartPrivileges mBalAllowedByPiCreator;
+        private final String mRealCallingPackage;
+        private final int mRealCallingUid;
+        private final int mRealCallingPid;
+        private final boolean mRealCallingUidHasAnyVisibleWindow;
+        private final @ActivityManager.ProcessState int mRealCallingUidProcState;
+        private final boolean mIsRealCallingUidPersistentSystemProcess;
+        private final PendingIntentRecord mOriginatingPendingIntent;
+        private final BackgroundStartPrivileges mBackgroundStartPrivileges;
+        private final Intent mIntent;
+        private final WindowProcessController mCallerApp;
+        private final WindowProcessController mRealCallerApp;
+
+        private BalState(int callingUid, int callingPid, final String callingPackage,
+                 int realCallingUid, int realCallingPid,
+                 WindowProcessController callerApp,
+                 PendingIntentRecord originatingPendingIntent,
+                 BackgroundStartPrivileges backgroundStartPrivileges,
+                 Intent intent,
+                 ActivityOptions checkedOptions) {
+            this.mCallingPackage = callingPackage;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            mRealCallingUid = realCallingUid;
+            mRealCallingPid = realCallingPid;
+            mCallerApp = callerApp;
+            mBackgroundStartPrivileges = backgroundStartPrivileges;
+            mOriginatingPendingIntent = originatingPendingIntent;
+            mIntent = intent;
+            mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+            mBalAllowedByPiSender =
+                    PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(checkedOptions,
+                            realCallingUid, mRealCallingPackage);
+            mBalAllowedByPiCreator =
+                    checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                            == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                            ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL;
+            mAppSwitchState = mService.getBalAppSwitchesState();
+            mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
+            mIsCallingUidPersistentSystemProcess =
+                    mCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+            mCallingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
+            if (callingUid == realCallingUid) {
+                mRealCallingUidProcState = mCallingUidProcState;
+                mRealCallingUidHasAnyVisibleWindow = mCallingUidHasAnyVisibleWindow;
+                // In the PendingIntent case callerApp is not passed in, so resolve it ourselves.
+                mRealCallerApp = callerApp == null
+                        ? mService.getProcessController(realCallingPid, realCallingUid)
+                        : callerApp;
+                mIsRealCallingUidPersistentSystemProcess = mIsCallingUidPersistentSystemProcess;
+            } else {
+                mRealCallingUidProcState = mService.mActiveUids.getUidState(realCallingUid);
+                mRealCallingUidHasAnyVisibleWindow =
+                        mService.hasActiveVisibleWindow(realCallingUid);
+                mRealCallerApp = mService.getProcessController(realCallingPid, realCallingUid);
+                mIsRealCallingUidPersistentSystemProcess =
+                        mRealCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+            }
+        }
+
+        private String getDebugPackageName(String packageName, int uid) {
+            if (packageName != null) {
+                return packageName; // use actual package
+            }
+            if (uid == 0) {
+                return "root[debugOnly]";
+            }
+            String name = mService.mContext.getPackageManager().getNameForUid(uid);
+            if (name == null) {
+                name = "uid=" + uid;
+            }
+            return name + "[debugOnly]";
+        }
+
+        private String dump(BalVerdict resultIfPiCreatorAllowsBal,
+                           BalVerdict resultIfPiSenderAllowsBal) {
+            return " [callingPackage: " + getDebugPackageName(mCallingPackage, mCallingUid)
+                    + "; callingUid: " + mCallingUid
+                    + "; appSwitchState: " + mAppSwitchState
+                    + "; callingUidHasAnyVisibleWindow: " + mCallingUidHasAnyVisibleWindow
+                    + "; callingUidProcState: " + DebugUtils.valueToString(
+                        ActivityManager.class, "PROCESS_STATE_", mCallingUidProcState)
+                    + "; isCallingUidPersistentSystemProcess: "
+                        + mIsCallingUidPersistentSystemProcess
+                    + "; balAllowedByPiCreator: " + mBalAllowedByPiCreator
+                    + "; balAllowedByPiSender: " + mBalAllowedByPiSender
+                    + "; realCallingPackage: "
+                        + getDebugPackageName(mRealCallingPackage, mRealCallingUid)
+                    + "; realCallingUid: " + mRealCallingUid
+                    + "; realCallingPid: " + mRealCallingPid
+                    + "; realCallingUidHasAnyVisibleWindow: " + mRealCallingUidHasAnyVisibleWindow
+                    + "; realCallingUidProcState: " + DebugUtils.valueToString(
+                        ActivityManager.class, "PROCESS_STATE_", mRealCallingUidProcState)
+                    + "; isRealCallingUidPersistentSystemProcess: "
+                        + mIsRealCallingUidPersistentSystemProcess
+                    + "; originatingPendingIntent: " + mOriginatingPendingIntent
+                    + "; backgroundStartPrivileges: " + mBackgroundStartPrivileges
+                    + "; intent: " + mIntent
+                    + "; callerApp: " + mCallerApp
+                    + "; realCallerApp: " + mRealCallerApp
+                    + "; inVisibleTask: "
+                        + (mCallerApp != null && mCallerApp.hasActivityInVisibleTask())
+                    + "; realInVisibleTask: "
+                        + (mRealCallerApp != null && mRealCallerApp.hasActivityInVisibleTask())
+                    + "; resultIfPiSenderAllowsBal: " + resultIfPiSenderAllowsBal
+                    + "; resultIfPiCreatorAllowsBal: " + resultIfPiCreatorAllowsBal
+                    + "]";
+        }
+    }
+
+    static class BalVerdict {
+
+        static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+        private final @BalCode int mCode;
+        private final boolean mBackground;
+        private final String mMessage;
+        private String mProcessInfo;
+
+        BalVerdict(@BalCode int balCode, boolean background, String message) {
+            this.mBackground = background;
+            this.mCode = balCode;
+            this.mMessage = message;
+        }
+
+        public BalVerdict withProcessInfo(String msg, WindowProcessController process) {
+            mProcessInfo = msg + " (uid=" + process.mUid + ",pid=" + process.getPid() + ")";
+            return this;
+        }
+
+        boolean blocks() {
+            return mCode == BAL_BLOCK;
+        }
+
+        boolean allows() {
+            return !blocks();
+        }
+
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            if (mBackground) {
+                builder.append("Background ");
+            }
+            builder.append("Activity start allowed: " + mMessage + ".");
+            builder.append("BAL Code: ");
+            builder.append(balCodeToString(mCode));
+            if (mProcessInfo != null) {
+                builder.append(" ");
+                builder.append(mProcessInfo);
+            }
+            return builder.toString();
+        }
+
+        public @BalCode int getCode() {
+            return mCode;
+        }
     }
 
     /**
      * @return A code denoting which BAL rule allows an activity to be started,
-     * or {@link BAL_BLOCK} if the launch should be blocked
+     * or {@link #BAL_BLOCK} if the launch should be blocked
      */
-    @BalCode
-    int checkBackgroundActivityStart(
+    BalVerdict checkBackgroundActivityStart(
             int callingUid,
             int callingPid,
             final String callingPackage,
@@ -221,36 +389,148 @@
             BackgroundStartPrivileges backgroundStartPrivileges,
             Intent intent,
             ActivityOptions checkedOptions) {
+
+        if (checkedOptions == null) {
+            // replace null with a constant to simplify evaluation
+            checkedOptions = ACTIVITY_OPTIONS_SYSTEM_DEFINED;
+        }
+
+        BalState state = new BalState(callingUid, callingPid, callingPackage,
+                realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
+                backgroundStartPrivileges, intent, checkedOptions);
+
+        // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
+        // visible window.
+        if (Process.isSdkSandboxUid(state.mRealCallingUid)) {
+            int realCallingSdkSandboxUidToAppUid =
+                    Process.getAppUidForSdkSandboxUid(state.mRealCallingUid);
+            // realCallingSdkSandboxUidToAppUid should probably just be used instead (or in addition
+            // to realCallingUid when calculating resultForRealCaller below.
+            if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
+                BalVerdict balVerdict = new BalVerdict(BAL_ALLOW_SDK_SANDBOX, /*background*/ false,
+                        "uid in SDK sandbox has visible (non-toast) window");
+                return statsLog(balVerdict, state);
+            }
+        }
+
+        BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
+        BalVerdict resultForRealCaller = callingUid == realCallingUid
+                ? resultForCaller // no need to calculate again
+                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+
+        if (resultForCaller.allows()
+                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "Activity start explicitly allowed by PI creator. "
+                        + state.dump(resultForCaller, resultForRealCaller));
+            }
+            return statsLog(resultForCaller, state);
+        }
+        if (resultForRealCaller.allows()
+                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "Activity start explicitly allowed by PI sender. "
+                        + state.dump(resultForCaller, resultForRealCaller));
+            }
+            return statsLog(resultForRealCaller, state);
+        }
+        if (resultForCaller.allows() && resultForRealCaller.allows()
+                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+            // Both caller and real caller allow with system defined behavior
+            Slog.wtf(TAG,
+                    "With Android 15 BAL hardening this activity start would be blocked"
+                            + " (missing opt in by PI creator)! "
+                            + state.dump(resultForCaller, resultForRealCaller));
+            // return the realCaller result for backwards compatibility
+            return statsLog(resultForRealCaller, state);
+        }
+        if (resultForCaller.allows()
+                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+            // Allowed before V by creator
+            Slog.wtf(TAG,
+                    "With Android 15 BAL hardening this activity start would be blocked"
+                            + " (missing opt in by PI creator)! "
+                            + state.dump(resultForCaller, resultForRealCaller));
+            return statsLog(resultForCaller, state);
+        }
+        if (resultForRealCaller.allows()
+                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+            // Allowed before U by sender
+            if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
+                Slog.wtf(TAG,
+                        "With Android 14 BAL hardening this activity start would be blocked"
+                                + " (missing opt in by PI sender)! "
+                                + state.dump(resultForCaller, resultForRealCaller));
+                return statsLog(resultForRealCaller, state);
+            }
+            Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
+                    + " (missing opt in by PI sender)! "
+                    + state.dump(resultForCaller, resultForRealCaller));
+            // fall through
+        }
+        // anything that has fallen through would currently be aborted
+        Slog.w(TAG, "Background activity launch blocked"
+                + state.dump(resultForCaller, resultForRealCaller));
+        // log aborted activity start to TRON
+        if (mService.isActivityStartsLoggingEnabled()) {
+            mSupervisor
+                    .getActivityMetricsLogger()
+                    .logAbortedBgActivityStart(
+                            intent,
+                            callerApp,
+                            callingUid,
+                            callingPackage,
+                            state.mCallingUidProcState,
+                            state.mCallingUidHasAnyVisibleWindow,
+                            realCallingUid,
+                            state.mRealCallingUidProcState,
+                            state.mRealCallingUidHasAnyVisibleWindow,
+                            (originatingPendingIntent != null));
+        }
+        return statsLog(BalVerdict.BLOCK, state);
+    }
+
+    /**
+     * @return A code denoting which BAL rule allows an activity to be started,
+     * or {@link #BAL_BLOCK} if the launch should be blocked
+     */
+    BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
+        int callingUid = state.mCallingUid;
+        int callingPid = state.mCallingPid;
+        final String callingPackage = state.mCallingPackage;
+        WindowProcessController callerApp = state.mCallerApp;
+
         // don't abort for the most important UIDs
         final int callingAppId = UserHandle.getAppId(callingUid);
-        final boolean useCallingUidState =
-                originatingPendingIntent == null
-                        || checkedOptions == null
-                        || checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                                != ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
-        if (useCallingUidState) {
-            if (callingUid == Process.ROOT_UID
-                    || callingAppId == Process.SYSTEM_UID
-                    || callingAppId == Process.NFC_UID) {
-                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
-                        callingUid, realCallingUid, intent, "Important callingUid");
-            }
+        if (callingUid == Process.ROOT_UID
+                || callingAppId == Process.SYSTEM_UID
+                || callingAppId == Process.NFC_UID) {
+            return new BalVerdict(
+                    BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
+                     "Important callingUid");
+        }
 
-            // Always allow home application to start activities.
-            if (isHomeApp(callingUid, callingPackage)) {
-                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                        /*background*/ false, callingUid, realCallingUid, intent,
-                        "Home app");
-            }
+        // Always allow home application to start activities.
+        if (isHomeApp(callingUid, callingPackage)) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ false,
+                    "Home app");
+        }
 
-            // IME should always be allowed to start activity, like IME settings.
-            final WindowState imeWindow =
-                    mService.mRootWindowContainer.getCurrentInputMethodWindow();
-            if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
-                return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                        /*background*/ false, callingUid, realCallingUid, intent,
-                        "Active ime");
-            }
+        // IME should always be allowed to start activity, like IME settings.
+        final WindowState imeWindow =
+                mService.mRootWindowContainer.getCurrentInputMethodWindow();
+        if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ false,
+                    "Active ime");
         }
 
         // This is used to block background activity launch even if the app is still
@@ -269,331 +549,169 @@
                 appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
         final boolean allowCallingUidStartActivity =
                 ((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
-                                && callingUidHasAnyVisibleWindow)
+                        && callingUidHasAnyVisibleWindow)
                         || isCallingUidPersistentSystemProcess;
-        if (useCallingUidState && allowCallingUidStartActivity) {
-            return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW,
-                    /*background*/ false, callingUid, realCallingUid, intent,
+        if (allowCallingUidStartActivity) {
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+                    /*background*/ false,
                     "callingUidHasAnyVisibleWindow = "
                             + callingUid
                             + ", isCallingUidPersistentSystemProcess = "
                             + isCallingUidPersistentSystemProcess);
         }
-        // take realCallingUid into consideration
-        final int realCallingUidProcState =
-                (callingUid == realCallingUid)
-                        ? callingUidProcState
-                        : mService.mActiveUids.getUidState(realCallingUid);
-        final boolean realCallingUidHasAnyVisibleWindow =
-                (callingUid == realCallingUid)
-                        ? callingUidHasAnyVisibleWindow
-                        : mService.hasActiveVisibleWindow(realCallingUid);
-        final int realCallingAppId = UserHandle.getAppId(realCallingUid);
-        final boolean isRealCallingUidPersistentSystemProcess =
-                (callingUid == realCallingUid)
-                        ? isCallingUidPersistentSystemProcess
-                        : (realCallingAppId == Process.SYSTEM_UID)
-                                || realCallingUidProcState
-                                        <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
 
-        // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
-        // visible window.
-        if (Process.isSdkSandboxUid(realCallingUid)) {
-            int realCallingSdkSandboxUidToAppUid =
-                    Process.getAppUidForSdkSandboxUid(realCallingUid);
-
-            if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
-                return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX,
-                        /*background*/ false, callingUid, realCallingUid, intent,
-                        "uid in SDK sandbox has visible (non-toast) window");
-            }
-        }
-
-        String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
-
-        // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
-        // The options here are the options passed by the sender and not those on the intent.
-        final BackgroundStartPrivileges balAllowedByPiSender =
-                PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
-                        checkedOptions, realCallingUid, realCallingPackage);
-
-        final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null
-                || checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                        == ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-        final boolean considerPiRules = logVerdictChangeByPiDefaultChange
-                || balAllowedByPiSender.allowsBackgroundActivityStarts();
-        final String verdictLogForPiSender =
-                balAllowedByPiSender.allowsBackgroundActivityStarts() ? VERDICT_ALLOWED
-                        : VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL;
-
-        @BalCode int resultIfPiSenderAllowsBal = BAL_BLOCK;
-        if (realCallingUid != callingUid && considerPiRules) {
-            resultIfPiSenderAllowsBal = checkPiBackgroundActivityStart(callingUid, realCallingUid,
-                backgroundStartPrivileges, intent, checkedOptions,
-                realCallingUidHasAnyVisibleWindow, isRealCallingUidPersistentSystemProcess,
-                verdictLogForPiSender);
-        }
-        if (resultIfPiSenderAllowsBal != BAL_BLOCK
-                && balAllowedByPiSender.allowsBackgroundActivityStarts()
-                && !logVerdictChangeByPiDefaultChange) {
-            // The result is to allow (because the sender allows BAL) and we are not interested in
-            // logging differences, so just return.
-            return resultIfPiSenderAllowsBal;
-        }
-        if (useCallingUidState) {
-            // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
-            if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
-                    callingPid, callingUid) == PERMISSION_GRANTED) {
-                return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION,
-                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                    /*background*/ true, callingUid, realCallingUid, intent,
+        // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
+        if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
+                callingPid, callingUid) == PERMISSION_GRANTED) {
+            return new BalVerdict(BAL_ALLOW_PERMISSION,
+                    /*background*/ true,
                     "START_ACTIVITIES_FROM_BACKGROUND permission granted");
-            }
-            // don't abort if the caller has the same uid as the recents component
-            if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
-                return logStartAllowedAndReturnCode(
-                    BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                    /*background*/ true, callingUid, realCallingUid,
-                    intent, "Recents Component");
-            }
-            // don't abort if the callingUid is the device owner
-            if (mService.isDeviceOwner(callingUid)) {
-                return logStartAllowedAndReturnCode(
-                    BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                    /*background*/ true, callingUid, realCallingUid,
-                    intent, "Device Owner");
-            }
-            // don't abort if the callingUid is a affiliated profile owner
-            if (mService.isAffiliatedProfileOwner(callingUid)) {
-                return logStartAllowedAndReturnCode(
-                    BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                    /*background*/ true, callingUid, realCallingUid,
-                    intent, "Affiliated Profile Owner");
-            }
-            // don't abort if the callingUid has companion device
-            final int callingUserId = UserHandle.getUserId(callingUid);
-            if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
-                return logStartAllowedAndReturnCode(
-                    BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                    /*background*/ true, callingUid, realCallingUid,
-                    intent, "Companion App");
-            }
-            // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
-            if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
-                Slog.w(
-                        TAG,
-                        "Background activity start for "
-                                + callingPackage
-                                + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
-                return logStartAllowedAndReturnCode(
-                    BAL_ALLOW_SAW_PERMISSION,
-                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                    /*background*/ true, callingUid, realCallingUid,
-                    intent, "SYSTEM_ALERT_WINDOW permission is granted");
-            }
-            // don't abort if the callingUid and callingPackage have the
-            // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
-            if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
-                        AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
-                        callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {
-                return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION,
-                        resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                        /*background*/ true, callingUid, realCallingUid, intent,
-                        "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
-            }
         }
+        // don't abort if the caller has the same uid as the recents component
+        if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ true, "Recents Component");
+        }
+        // don't abort if the callingUid is the device owner
+        if (mService.isDeviceOwner(callingUid)) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ true, "Device Owner");
+        }
+        // don't abort if the callingUid is a affiliated profile owner
+        if (mService.isAffiliatedProfileOwner(callingUid)) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ true, "Affiliated Profile Owner");
+        }
+        // don't abort if the callingUid has companion device
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ true, "Companion App");
+        }
+        // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
+        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
+            Slog.w(
+                    TAG,
+                    "Background activity start for "
+                            + callingPackage
+                            + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
+            return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
+                    /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+        }
+        // don't abort if the callingUid and callingPackage have the
+        // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
+        if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
+                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+                callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {
+            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+                    "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
+        }
+
         // If we don't have callerApp at this point, no caller was provided to startActivity().
         // That's the case for PendingIntent-based starts, since the creator's process might not be
-        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
-        // caller if caller allows, so that we can make the decision based on its state.
-        int callerAppUid = callingUid;
-        boolean callerAppBasedOnPiSender = callerApp == null && considerPiRules
-                && resultIfPiSenderAllowsBal == BAL_BLOCK;
-        if (callerAppBasedOnPiSender) {
-            callerApp = mService.getProcessController(realCallingPid, realCallingUid);
-            callerAppUid = realCallingUid;
-        }
-        // don't abort if the callerApp or other processes of that uid are allowed in any way
-        if (callerApp != null && (useCallingUidState || callerAppBasedOnPiSender)) {
-            // first check the original calling process
-            final @BalCode int balAllowedForCaller = callerApp
-                    .areBackgroundActivityStartsAllowed(appSwitchState);
-            if (balAllowedForCaller != BAL_BLOCK) {
-                if (callerAppBasedOnPiSender) {
-                    resultIfPiSenderAllowsBal = logStartAllowedAndReturnCode(balAllowedForCaller,
-                        /*background*/ true, callingUid, realCallingUid, intent,
-                        "callerApp process (pid = " + callerApp.getPid()
-                            + ", uid = " + callerAppUid + ") is allowed", verdictLogForPiSender);
-                } else {
-                    return logStartAllowedAndReturnCode(balAllowedForCaller,
-                        resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                        /*background*/ true, callingUid, realCallingUid, intent,
-                        "callerApp process (pid = " + callerApp.getPid()
-                            + ", uid = " + callerAppUid + ") is allowed");
-                }
-            } else {
-                // only if that one wasn't allowed, check the other ones
-                final ArraySet<WindowProcessController> uidProcesses =
-                    mService.mProcessMap.getProcesses(callerAppUid);
-                if (uidProcesses != null) {
-                    for (int i = uidProcesses.size() - 1; i >= 0; i--) {
-                        final WindowProcessController proc = uidProcesses.valueAt(i);
-                        int balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
-                                appSwitchState);
-                        if (proc != callerApp && balAllowedForUid != BAL_BLOCK) {
-                            if (callerAppBasedOnPiSender) {
-                                resultIfPiSenderAllowsBal = logStartAllowedAndReturnCode(
-                                    balAllowedForUid,
-                                    /*background*/ true, callingUid, realCallingUid, intent,
-                                    "process" + proc.getPid() + " from uid " + callerAppUid
-                                        + " is allowed", verdictLogForPiSender);
-                                break;
-                            } else {
-                                return logStartAllowedAndReturnCode(balAllowedForUid,
-                                    resultIfPiSenderAllowsBal, balAllowedByPiSender,
-                                    /*background*/ true, callingUid, realCallingUid, intent,
-                                    "process" + proc.getPid() + " from uid " + callerAppUid
-                                        + " is allowed");
-                            }
-                        }
-                    }
-                }
-            }
-            if (callerAppBasedOnPiSender) {
-                // If caller app was based on PI sender, this result is part of
-                // resultIfPiSenderAllowsBal
-                if (resultIfPiSenderAllowsBal != BAL_BLOCK
-                        && balAllowedByPiSender.allowsBackgroundActivityStarts()
-                        && !logVerdictChangeByPiDefaultChange) {
-                    // The result is to allow (because the sender allows BAL) and we are not
-                    // interested in logging differences, so just return.
-                    return resultIfPiSenderAllowsBal;
-                }
-            } else {
-                // If caller app was NOT based on PI sender and we found a allow reason we should
-                // have returned already
-                checkState(balAllowedForCaller == BAL_BLOCK,
-                        "balAllowedForCaller = " + balAllowedForCaller + " (should have returned)");
-            }
-        }
-        // If we are here, it means all exemptions not based on PI sender failed, so we'll block
-        // unless resultIfPiSenderAllowsBal is an allow and the PI sender allows BAL
-
-        if (realCallingPackage == null) {
-            realCallingPackage = (callingUid == realCallingUid ? callingPackage :
-                    mService.mContext.getPackageManager().getNameForUid(realCallingUid))
-                    + "[debugOnly]";
+        // up and alive.
+        // Don't abort if the callerApp or other processes of that uid are allowed in any way.
+        BalVerdict callerAppAllowsBal = checkProcessAllowsBal(callerApp, state);
+        if (callerAppAllowsBal.allows()) {
+            return callerAppAllowsBal;
         }
 
-        String stateDumpLog = " [callingPackage: " + callingPackage
-                + "; callingUid: " + callingUid
-                + "; appSwitchState: " + appSwitchState
-                + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow
-                + "; callingUidProcState: " + DebugUtils.valueToString(
-                        ActivityManager.class, "PROCESS_STATE_", callingUidProcState)
-                + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
-                + "; balAllowedByPiSender: " + balAllowedByPiSender
-                + "; realCallingPackage: " + realCallingPackage
-                + "; realCallingUid: " + realCallingUid
-                + "; realCallingUidHasAnyVisibleWindow: " + realCallingUidHasAnyVisibleWindow
-                + "; realCallingUidProcState: " + DebugUtils.valueToString(
-                        ActivityManager.class, "PROCESS_STATE_", realCallingUidProcState)
-                + "; isRealCallingUidPersistentSystemProcess: "
-                        + isRealCallingUidPersistentSystemProcess
-                + "; originatingPendingIntent: " + originatingPendingIntent
-                + "; backgroundStartPrivileges: " + backgroundStartPrivileges
-                + "; intent: " + intent
-                + "; callerApp: " + callerApp
-                + "; inVisibleTask: " + (callerApp != null && callerApp.hasActivityInVisibleTask())
-                + "; resultIfPiSenderAllowsBal: " + balCodeToString(resultIfPiSenderAllowsBal)
-                + "]";
-        if (resultIfPiSenderAllowsBal != BAL_BLOCK) {
-            // We should have returned before if !logVerdictChangeByPiDefaultChange
-            checkState(logVerdictChangeByPiDefaultChange,
-                    "resultIfPiSenderAllowsBal = " + balCodeToString(resultIfPiSenderAllowsBal)
-                        + " at the end but logVerdictChangeByPiDefaultChange = false");
-            if (balAllowedByPiSender.allowsBackgroundActivityStarts()) {
-                // The verdict changed from block to allow, PI sender default change is off and
-                // we'd block if it were on
-                Slog.wtf(TAG, "With BAL hardening this activity start would be blocked!"
-                        + stateDumpLog);
-                return resultIfPiSenderAllowsBal;
-            } else {
-                // The verdict changed from allow (resultIfPiSenderAllowsBal) to block, PI sender
-                // default change is on (otherwise we would have fallen into if above) and we'd
-                // allow if it were off
-                Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed!"
-                        + stateDumpLog);
-            }
-        }
-        // anything that has fallen through would currently be aborted
-        Slog.w(TAG, "Background activity launch blocked" + stateDumpLog);
-        // log aborted activity start to TRON
-        if (mService.isActivityStartsLoggingEnabled()) {
-            mSupervisor
-                    .getActivityMetricsLogger()
-                    .logAbortedBgActivityStart(
-                            intent,
-                            callerApp,
-                            callingUid,
-                            callingPackage,
-                            callingUidProcState,
-                            callingUidHasAnyVisibleWindow,
-                            realCallingUid,
-                            realCallingUidProcState,
-                            realCallingUidHasAnyVisibleWindow,
-                            (originatingPendingIntent != null));
-        }
-        return BAL_BLOCK;
+        // If we are here, it means all exemptions based on the creator failed
+        return BalVerdict.BLOCK;
     }
 
-    private @BalCode int checkPiBackgroundActivityStart(int callingUid, int realCallingUid,
-            BackgroundStartPrivileges backgroundStartPrivileges, Intent intent,
-            ActivityOptions checkedOptions, boolean realCallingUidHasAnyVisibleWindow,
-            boolean isRealCallingUidPersistentSystemProcess, String verdictLog) {
-        final boolean useCallerPermission =
-                PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions);
-        if (useCallerPermission
+    /**
+     * @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) {
+        int realCallingUid = state.mRealCallingUid;
+        BackgroundStartPrivileges backgroundStartPrivileges = state.mBackgroundStartPrivileges;
+
+        if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
                 && ActivityManager.checkComponentPermission(
-                                android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+                android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                 realCallingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
-            return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
-                /*background*/ false, callingUid, realCallingUid, intent,
-                "realCallingUid has BAL permission. realCallingUid: " + realCallingUid,
-                verdictLog);
+            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                    /*background*/ false,
+                    "realCallingUid has BAL permission. realCallingUid: " + realCallingUid);
         }
 
         // don't abort if the realCallingUid has a visible window
         // TODO(b/171459802): We should check appSwitchAllowed also
-        if (realCallingUidHasAnyVisibleWindow) {
-            return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
-                    /*background*/ false, callingUid, realCallingUid, intent,
+        if (state.mRealCallingUidHasAnyVisibleWindow) {
+            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                    /*background*/ false,
                     "realCallingUid has visible (non-toast) window. realCallingUid: "
-                            + realCallingUid, verdictLog);
+                            + realCallingUid);
         }
         // if the realCallingUid is a persistent system process, abort if the IntentSender
         // wasn't allowed to start an activity
-        if (isRealCallingUidPersistentSystemProcess
+        if (state.mIsRealCallingUidPersistentSystemProcess
                 && backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
-            return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
-                    /*background*/ false, callingUid, realCallingUid, intent,
+            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                    /*background*/ false,
                     "realCallingUid is persistent system process AND intent "
                             + "sender allowed (allowBackgroundActivityStart = true). "
-                            + "realCallingUid: " + realCallingUid, verdictLog);
+                            + "realCallingUid: " + realCallingUid);
         }
         // don't abort if the realCallingUid is an associated companion app
         if (mService.isAssociatedCompanionApp(
                 UserHandle.getUserId(realCallingUid), realCallingUid)) {
-            return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
-                    /*background*/ false, callingUid, realCallingUid, intent,
+            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                    /*background*/ false,
                     "realCallingUid is a companion app. "
-                            + "realCallingUid: " + realCallingUid, verdictLog);
+                            + "realCallingUid: " + realCallingUid);
         }
-        return BAL_BLOCK;
+
+        // don't abort if the callerApp or other processes of that uid are allowed in any way
+        BalVerdict realCallerAppAllowsBal =
+                checkProcessAllowsBal(state.mRealCallerApp, state);
+        if (realCallerAppAllowsBal.allows()) {
+            return realCallerAppAllowsBal;
+        }
+
+        // If we are here, it means all exemptions based on PI sender failed
+        return BalVerdict.BLOCK;
+    }
+
+    /**
+     * Check if the app allows BAL.
+     *
+     * See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int,
+     * String, int, boolean, boolean, boolean, long, long, long)} for details on the
+     * exceptions.
+     */
+    private BalVerdict checkProcessAllowsBal(WindowProcessController app,
+            BalState state) {
+        if (app == null) {
+            return BalVerdict.BLOCK;
+        }
+        // first check the original calling process
+        final BalVerdict balAllowedForCaller = app
+                .areBackgroundActivityStartsAllowed(state.mAppSwitchState);
+        if (balAllowedForCaller.allows()) {
+            return balAllowedForCaller.withProcessInfo("callerApp process", app);
+        } else {
+            // only if that one wasn't allowed, check the other ones
+            final ArraySet<WindowProcessController> uidProcesses =
+                    mService.mProcessMap.getProcesses(app.mUid);
+            if (uidProcesses != null) {
+                for (int i = uidProcesses.size() - 1; i >= 0; i--) {
+                    final WindowProcessController proc = uidProcesses.valueAt(i);
+                    if (proc != app) {
+                        BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
+                                state.mAppSwitchState);
+                        if (balAllowedForUid.allows()) {
+                            return balAllowedForCaller.withProcessInfo("process", proc);
+                        }
+                    }
+                }
+            }
+        }
+        return BalVerdict.BLOCK;
     }
 
     /**
@@ -1091,55 +1209,6 @@
         return joiner.toString();
     }
 
-    static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
-            int callingUid, int realCallingUid, Intent intent, int pid, String msg) {
-        return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
-                DEBUG_ACTIVITY_STARTS ?  ("[Process(" + pid + ")]" + msg) : "");
-    }
-
-    static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
-            int callingUid, int realCallingUid, Intent intent, String msg) {
-        return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
-            msg, VERDICT_ALLOWED);
-    }
-
-    /**
-     * Logs the start and returns one of the provided codes depending on if the PI sender allows
-     * using its BAL privileges.
-     */
-    static @BalCode int logStartAllowedAndReturnCode(@BalCode int result,
-            @BalCode int resultIfPiSenderAllowsBal, BackgroundStartPrivileges balAllowedByPiSender,
-            boolean background, int callingUid, int realCallingUid, Intent intent, String msg) {
-        if (resultIfPiSenderAllowsBal != BAL_BLOCK
-                && balAllowedByPiSender.allowsBackgroundActivityStarts()) {
-            // resultIfPiSenderAllowsBal was already logged, so just return
-            return resultIfPiSenderAllowsBal;
-        }
-        return logStartAllowedAndReturnCode(result, background, callingUid, realCallingUid,
-            intent, msg, VERDICT_ALLOWED);
-    }
-
-
-    static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
-            int callingUid, int realCallingUid, Intent intent, String msg, String verdict) {
-        statsLogBalAllowed(code, callingUid, realCallingUid, intent);
-        if (DEBUG_ACTIVITY_STARTS) {
-            StringBuilder builder = new StringBuilder();
-            if (background) {
-                builder.append("Background ");
-            }
-            builder.append(verdict + ": " + msg + ". callingUid: " + callingUid + ". ");
-            builder.append("BAL Code: ");
-            builder.append(balCodeToString(code));
-            if (verdict.equals(VERDICT_ALLOWED)) {
-                Slog.i(TAG, builder.toString());
-            } else {
-                Slog.d(TAG, builder.toString());
-            }
-        }
-        return code;
-    }
-
     private static boolean isSystemExemptFlagEnabled() {
         return DeviceConfig.getBoolean(
                 NAMESPACE_WINDOW_MANAGER,
@@ -1147,8 +1216,12 @@
                 /* defaultValue= */ true);
     }
 
-    private static void statsLogBalAllowed(
-            @BalCode int code, int callingUid, int realCallingUid, Intent intent) {
+    private static BalVerdict statsLog(BalVerdict finalVerdict, BalState state) {
+        @BalCode int code = finalVerdict.getCode();
+        int callingUid = state.mCallingUid;
+        int realCallingUid = state.mRealCallingUid;
+        Intent intent = state.mIntent;
+
         if (code == BAL_ALLOW_PENDING_INTENT
                 && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
             String activityName =
@@ -1168,6 +1241,7 @@
                     callingUid,
                     realCallingUid);
         }
+        return finalVerdict;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 527edc1..e849589 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -27,7 +27,6 @@
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 
 import static java.util.Objects.requireNonNull;
 
@@ -49,6 +48,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -96,37 +96,33 @@
         mBackgroundActivityStartCallback = callback;
     }
 
-    @BackgroundActivityStartController.BalCode
-    int areBackgroundActivityStartsAllowed(int pid, int uid, String packageName,
+    BalVerdict areBackgroundActivityStartsAllowed(
+            int pid, int uid, String packageName,
             int appSwitchState, boolean isCheckingForFgsStart,
             boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
             long lastStopAppSwitchesTime, long lastActivityLaunchTime,
             long lastActivityFinishTime) {
         // Allow if the proc is instrumenting with background activity starts privs.
         if (hasBackgroundActivityStartPrivileges) {
-            return BackgroundActivityStartController.logStartAllowedAndReturnCode(
-                    BAL_ALLOW_PERMISSION, /*background*/ true, uid, uid, /*intent*/ null,
-                    pid, "Activity start allowed: process instrumenting with background "
-                        + "activity starts privileges");
+            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+                    "Activity start allowed: process instrumenting with background "
+                            + "activity starts privileges");
         }
         // Allow if the flag was explicitly set.
         if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
-            return BackgroundActivityStartController.logStartAllowedAndReturnCode(
-                    BAL_ALLOW_PERMISSION, /*background*/ true, uid, uid, /*intent*/ null,
-                    pid, "Activity start allowed: process allowed by token");
+            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+                    "Activity start allowed: process allowed by token");
         }
         // Allow if the caller is bound by a UID that's currently foreground.
         if (isBoundByForegroundUid()) {
-            return BackgroundActivityStartController.logStartAllowedAndReturnCode(
-                    BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, uid, uid, /*intent*/ null,
-                    pid, "Activity start allowed: process bound by foreground uid");
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+                    "Activity start allowed: process bound by foreground uid");
         }
         // Allow if the caller has an activity in any foreground task.
         if (hasActivityInVisibleTask
                 && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
-            return BackgroundActivityStartController.logStartAllowedAndReturnCode(
-                    BAL_ALLOW_FOREGROUND, /*background*/ false, uid, uid, /*intent*/ null,
-                    pid, "Activity start allowed: process has activity in foreground task");
+            return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
+                    "Activity start allowed: process has activity in foreground task");
         }
 
         // If app switching is not allowed, we ignore all the start activity grace period
@@ -141,9 +137,8 @@
                 // let app to be able to start background activity even it's in grace period.
                 if (lastActivityLaunchTime > lastStopAppSwitchesTime
                         || lastActivityFinishTime > lastStopAppSwitchesTime) {
-                    return BackgroundActivityStartController.logStartAllowedAndReturnCode(
-                            BAL_ALLOW_GRACE_PERIOD, /*background*/ true, uid, uid, /*intent*/ null,
-                            pid, "Activity start allowed: within "
+                    return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
+                            "Activity start allowed: within "
                                     + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
                 }
                 if (DEBUG_ACTIVITY_STARTS) {
@@ -154,7 +149,7 @@
 
             }
         }
-        return BAL_BLOCK;
+        return BalVerdict.BLOCK;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
index d604402..5db02df 100644
--- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
@@ -58,7 +58,7 @@
      * @param id The id of task that has been removed.
      * @param userId The id of the user the task belonged to.
      */
-    void removeSnap(int id, int userId) {
+    void removeSnapshot(int id, int userId) {
         synchronized (mLock) {
             mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
                     .createDeleteWriteQueueItem(id, userId, mPersistInfoProvider));
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 58d4e82..7947112 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -223,9 +223,9 @@
     }
 
     /**
-     * Update merged override configuration based on corresponding parent's config and notify all
-     * its children. If there is no parent, merged override configuration will set equal to current
-     * override config.
+     * Update merged override configuration based on corresponding parent's config. If there is no
+     * parent, merged override configuration will set equal to current override config. This
+     * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}.
      * @see #mMergedOverrideConfiguration
      */
     void onMergedOverrideConfigurationChanged() {
@@ -240,10 +240,6 @@
         } else {
             mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration);
         }
-        for (int i = getChildCount() - 1; i >= 0; --i) {
-            final ConfigurationContainer child = getChildAt(i);
-            child.onMergedOverrideConfigurationChanged();
-        }
     }
 
     /**
@@ -688,8 +684,6 @@
         if (newParent != null) {
             // Update full configuration of this container and all its children.
             onConfigurationChanged(newParent.mFullConfiguration);
-            // Update merged override configuration of this container and all its children.
-            onMergedOverrideConfigurationChanged();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 06448d0..022ef61 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -28,6 +28,7 @@
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.media.projection.IMediaProjectionManager;
 import android.os.IBinder;
@@ -36,10 +37,12 @@
 import android.view.ContentRecordingSession;
 import android.view.ContentRecordingSession.RecordContent;
 import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 /**
  * Manages content recording for a particular {@link DisplayContent}.
@@ -47,6 +50,16 @@
 final class ContentRecorder implements WindowContainerListener {
 
     /**
+     * Maximum acceptable anisotropy for the output image.
+     *
+     * Necessary to avoid unnecessary scaling when the anisotropy is almost the same, as it is not
+     * exact anyway. For external displays, we expect an anisoptry of about 2% even if the pixels
+     * are, in fact, square due to the imprecision of the display's actual size (rounded to the
+     * nearest cm).
+     */
+    private static final float MAX_ANISOTROPY = 0.025f;
+
+    /**
      * The display content this class is handling recording for.
      */
     @NonNull
@@ -87,15 +100,20 @@
     @Configuration.Orientation
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
+    private final boolean mCorrectForAnisotropicPixels;
+
     ContentRecorder(@NonNull DisplayContent displayContent) {
-        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
+        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
+                new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
     }
 
     @VisibleForTesting
     ContentRecorder(@NonNull DisplayContent displayContent,
-            @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
+            @NonNull MediaProjectionManagerWrapper mediaProjectionManager,
+            boolean correctForAnisotropicPixels) {
         mDisplayContent = displayContent;
         mMediaProjectionManager = mediaProjectionManager;
+        mCorrectForAnisotropicPixels = correctForAnisotropicPixels;
     }
 
     /**
@@ -460,6 +478,33 @@
         }
     }
 
+    private void computeScaling(int inputSizeX, int inputSizeY,
+            float inputDpiX, float inputDpiY,
+            int outputSizeX, int outputSizeY,
+            float outputDpiX, float outputDpiY,
+            PointF scaleOut) {
+        float relAnisotropy = (inputDpiY / inputDpiX) / (outputDpiY / outputDpiX);
+        if (!mCorrectForAnisotropicPixels
+                || (relAnisotropy > (1 - MAX_ANISOTROPY) && relAnisotropy < (1 + MAX_ANISOTROPY))) {
+            // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
+            // output surface.
+            float scaleX = outputSizeX / (float) inputSizeX;
+            float scaleY = outputSizeY / (float) inputSizeY;
+            float scale = Math.min(scaleX, scaleY);
+            scaleOut.x = scale;
+            scaleOut.y = scale;
+            return;
+        }
+
+        float relDpiX = outputDpiX / inputDpiX;
+        float relDpiY = outputDpiY / inputDpiY;
+
+        float scale = Math.min(outputSizeX / relDpiX / inputSizeX,
+                outputSizeY / relDpiY / inputSizeY);
+        scaleOut.x = scale * relDpiX;
+        scaleOut.y = scale * relDpiY;
+    }
+
     /**
      * Apply transformations to the mirrored surface to ensure the captured contents are scaled to
      * fit and centred in the output surface.
@@ -473,13 +518,19 @@
      */
     @VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction,
             Rect recordedContentBounds, Point surfaceSize) {
-        // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
-        // output surface.
-        float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
-        float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
-        float scale = Math.min(scaleX, scaleY);
-        int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
-        int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());
+
+        DisplayInfo inputDisplayInfo = mRecordedWindowContainer.mDisplayContent.getDisplayInfo();
+        DisplayInfo outputDisplayInfo = mDisplayContent.getDisplayInfo();
+
+        PointF scale = new PointF();
+        computeScaling(recordedContentBounds.width(), recordedContentBounds.height(),
+                inputDisplayInfo.physicalXDpi, inputDisplayInfo.physicalYDpi,
+                surfaceSize.x, surfaceSize.y,
+                outputDisplayInfo.physicalXDpi, outputDisplayInfo.physicalYDpi,
+                scale);
+
+        int scaledWidth = Math.round(scale.x * (float) recordedContentBounds.width());
+        int scaledHeight = Math.round(scale.y * (float) recordedContentBounds.height());
 
         // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
         // contents in the output surface.
@@ -493,10 +544,10 @@
         }
 
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka "
-                        + "recorded content size) %d x %d for display %d; display has size %d x "
-                        + "%d; surface has size %d x %d",
-                shiftedX, shiftedY, scale, recordedContentBounds.width(),
+                "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop "
+                        + "(aka recorded content size) %d x %d for display %d; display has size "
+                        + "%d x %d; surface has size %d x %d",
+                shiftedX, shiftedY, scale.x, scale.y, recordedContentBounds.width(),
                 recordedContentBounds.height(), mDisplayContent.getDisplayId(),
                 mDisplayContent.getConfiguration().screenWidthDp,
                 mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y);
@@ -508,7 +559,7 @@
                         recordedContentBounds.height())
                 // Scale the root mirror SurfaceControl, based upon the size difference between the
                 // source (DisplayArea to capture) and output (surface the app reads images from).
-                .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
+                .setMatrix(mRecordedSurface, scale.x, 0 /* dtdx */, 0 /* dtdy */, scale.y)
                 // Position needs to be updated when the mirrored DisplayArea has changed, since
                 // the content will no longer be centered in the output surface.
                 .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index ae29afa..2fabb0e 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,172 +11,39 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.wm;
 
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-
 import android.annotation.NonNull;
 import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-import java.io.PrintWriter;
+import com.android.window.flags.Flags;
 
 /**
  * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
  * black layers of varying opacity at various Z-levels which create the effect of a Dim.
  */
-class Dimmer {
-    private static final String TAG = "WindowManager";
-    // This is in milliseconds.
-    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+public abstract class Dimmer {
 
-    private class DimAnimatable implements SurfaceAnimator.Animatable {
-        private SurfaceControl mDimLayer;
-
-        private DimAnimatable(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-        }
-
-        @Override
-        public SurfaceControl.Transaction getSyncTransaction() {
-            return mHost.getSyncTransaction();
-        }
-
-        @Override
-        public SurfaceControl.Transaction getPendingTransaction() {
-            return mHost.getPendingTransaction();
-        }
-
-        @Override
-        public void commitPendingTransaction() {
-            mHost.commitPendingTransaction();
-        }
-
-        @Override
-        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
-        }
-
-        @Override
-        public void onAnimationLeashLost(SurfaceControl.Transaction t) {
-        }
-
-        @Override
-        public SurfaceControl.Builder makeAnimationLeash() {
-            return mHost.makeAnimationLeash();
-        }
-
-        @Override
-        public SurfaceControl getAnimationLeashParent() {
-            return mHost.getSurfaceControl();
-        }
-
-        @Override
-        public SurfaceControl getSurfaceControl() {
-            return mDimLayer;
-        }
-
-        @Override
-        public SurfaceControl getParentSurfaceControl() {
-            return mHost.getSurfaceControl();
-        }
-
-        @Override
-        public int getSurfaceWidth() {
-            // This will determine the size of the leash created. This should be the size of the
-            // host and not the dim layer since the dim layer may get bigger during animation. If
-            // that occurs, the leash size cannot change so we need to ensure the leash is big
-            // enough that the dim layer can grow.
-            // This works because the mHost will be a Task which has the display bounds.
-            return mHost.getSurfaceWidth();
-        }
-
-        @Override
-        public int getSurfaceHeight() {
-            // See getSurfaceWidth() above for explanation.
-            return mHost.getSurfaceHeight();
-        }
-
-        void removeSurface() {
-            if (mDimLayer != null && mDimLayer.isValid()) {
-                getSyncTransaction().remove(mDimLayer);
-            }
-            mDimLayer = null;
-        }
-    }
-
-    @VisibleForTesting
-    class DimState {
-        /**
-         * The layer where property changes should be invoked on.
-         */
-        SurfaceControl mDimLayer;
-        boolean mDimming;
-        boolean isVisible;
-        SurfaceAnimator mSurfaceAnimator;
-
-        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
-        final Rect mDimBounds = new Rect();
-
-        /**
-         * Determines whether the dim layer should animate before destroying.
-         */
-        boolean mAnimateExit = true;
-
-        /**
-         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
-         * details on Dim lifecycle.
-         */
-        boolean mDontReset;
-
-        DimState(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-            mDimming = true;
-            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
-            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
-                if (!mDimming) {
-                    dimAnimatable.removeSurface();
-                }
-            }, mHost.mWmService);
-        }
-    }
+    static final boolean DIMMER_REFACTOR = Flags.introduceSmootherDimmer();
 
     /**
-     * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+     * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
      * host, some controller of it, or one of the hosts children.
      */
-    private WindowContainer mHost;
-    private WindowContainer mLastRequestedDimContainer;
-    @VisibleForTesting
-    DimState mDimState;
+    protected final WindowContainer mHost;
 
-    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
-    @VisibleForTesting
-    interface SurfaceAnimatorStarter {
-        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden, @AnimationType int type);
-    }
-
-    Dimmer(WindowContainer host) {
-        this(host, SurfaceAnimator::startAnimation);
-    }
-
-    Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+    protected Dimmer(WindowContainer host) {
         mHost = host;
-        mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+    }
+
+    // Constructs the correct type of dimmer
+    static Dimmer create(WindowContainer host) {
+        return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host);
     }
 
     @NonNull
@@ -184,73 +51,34 @@
         return mHost;
     }
 
-    private SurfaceControl makeDimLayer() {
-        return mHost.makeChildSurface(null)
-                .setParent(mHost.getSurfaceControl())
-                .setColorLayer()
-                .setName("Dim Layer for - " + mHost.getName())
-                .setCallsite("Dimmer.makeDimLayer")
-                .build();
-    }
-
     /**
-     * Retrieve the DimState, creating one if it doesn't exist.
-     */
-    private DimState getDimState(WindowContainer container) {
-        if (mDimState == null) {
-            try {
-                final SurfaceControl ctl = makeDimLayer();
-                mDimState = new DimState(ctl);
-            } catch (Surface.OutOfResourcesException e) {
-                Log.w(TAG, "OutOfResourcesException creating dim surface");
-            }
-        }
-
-        mLastRequestedDimContainer = container;
-        return mDimState;
-    }
-
-    private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
-        final DimState d = getDimState(container);
-
-        if (d == null) {
-            return;
-        }
-
-        // The dim method is called from WindowState.prepareSurfaces(), which is always called
-        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
-        // relative to the highest Z layer with a dim.
-        SurfaceControl.Transaction t = mHost.getPendingTransaction();
-        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
-        t.setAlpha(d.mDimLayer, alpha);
-        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-
-        d.mDimming = true;
-    }
-
-    /**
-     * Place a dim above the given container, which should be a child of the host container.
-     * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
-     * and the child should call dimAbove again to request the Dim to continue.
+     * Position the dim relatively to the dimming container.
+     * Normally called together with #setAppearance, it can be called alone to keep the dim parented
+     * to a visible container until the next dimming container is ready.
+     * If multiple containers call this method, only the changes relative to the topmost will be
+     * applied.
      *
-     * @param container The container which to dim above. Should be a child of our host.
-     * @param alpha     The alpha at which to Dim.
+     * For each call to {@link WindowContainer#prepareSurfaces()} the DimState will be reset, and
+     * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
+     * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
+     * without also adjusting the appearance.
+     * @param container      The container which to dim above. Should be a child of the host.
+     * @param relativeLayer  The position of the dim wrt the container
      */
-    void dimAbove(@NonNull WindowContainer container, float alpha) {
-        dim(container, 1, alpha, 0);
-    }
+    protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer);
 
     /**
-     * Like {@link #dimAbove} but places the dim below the given container.
-     *
-     * @param container  The container which to dim below. Should be a child of our host.
-     * @param alpha      The alpha at which to Dim.
-     * @param blurRadius The amount of blur added to the Dim.
+     * Set the aspect of the dim layer, and request to keep dimming.
+     * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
+     * child should call setAppearance again to request the Dim to continue.
+     * If multiple containers call this method, only the changes relative to the topmost will be
+     * applied.
+     * @param container  Container requesting the dim
+     * @param alpha      Dim amount
+     * @param blurRadius Blur amount
      */
-
-    void dimBelow(@NonNull WindowContainer container, float alpha, int blurRadius) {
-        dim(container, -1, alpha, blurRadius);
-    }
+    protected abstract void adjustAppearance(
+            WindowContainer container, float alpha, int blurRadius);
 
     /**
      * Mark all dims as pending completion on the next call to {@link #updateDims}
@@ -260,25 +88,15 @@
      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
      * a chance to request dims to continue.
      */
-    void resetDimStates() {
-        if (mDimState == null) {
-            return;
-        }
-        if (!mDimState.mDontReset) {
-            mDimState.mDimming = false;
-        }
-    }
+    abstract void resetDimStates();
 
     /** Returns non-null bounds if the dimmer is showing. */
-    Rect getDimBounds() {
-        return mDimState != null ? mDimState.mDimBounds : null;
-    }
+    abstract Rect getDimBounds();
 
-    void dontAnimateExit() {
-        if (mDimState != null) {
-            mDimState.mAnimateExit = false;
-        }
-    }
+    abstract void dontAnimateExit();
+
+    @VisibleForTesting
+    abstract SurfaceControl getDimLayer();
 
     /**
      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -288,109 +106,5 @@
      * @param t      A transaction in which to update the dims.
      * @return true if any Dims were updated.
      */
-    boolean updateDims(SurfaceControl.Transaction t) {
-        if (mDimState == null) {
-            return false;
-        }
-
-        if (!mDimState.mDimming) {
-            if (!mDimState.mAnimateExit) {
-                if (mDimState.mDimLayer.isValid()) {
-                    t.remove(mDimState.mDimLayer);
-                }
-            } else {
-                startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
-            }
-            mDimState = null;
-            return false;
-        } else {
-            final Rect bounds = mDimState.mDimBounds;
-            // TODO: Once we use geometry from hierarchy this falls away.
-            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
-            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
-            if (!mDimState.isVisible) {
-                mDimState.isVisible = true;
-                t.show(mDimState.mDimLayer);
-                // Skip enter animation while starting window is on top of its activity
-                final WindowState ws = mLastRequestedDimContainer.asWindowState();
-                if (ws == null || ws.mActivityRecord == null
-                        || ws.mActivityRecord.mStartingData == null) {
-                    startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
-                }
-            }
-            return true;
-        }
-    }
-
-    private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t) {
-        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
-    }
-
-    private void startDimExit(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t) {
-        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
-    }
-
-    private void startAnim(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
-        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
-                new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
-                mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
-                ANIMATION_TYPE_DIMMER);
-    }
-
-    private long getDimDuration(WindowContainer container) {
-        // If there's no container, then there isn't an animation occurring while dimming. Set the
-        // duration to 0 so it immediately dims to the set alpha.
-        if (container == null) {
-            return 0;
-        }
-
-        // Otherwise use the same duration as the animation on the WindowContainer
-        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
-        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
-        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
-                : animationAdapter.getDurationHint();
-    }
-
-    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
-        private final long mDuration;
-        private final float mFromAlpha;
-        private final float mToAlpha;
-
-        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
-            mFromAlpha = fromAlpha;
-            mToAlpha = toAlpha;
-            mDuration = duration;
-        }
-
-        @Override
-        public long getDuration() {
-            return mDuration;
-        }
-
-        @Override
-        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
-            final float fraction = getFraction(currentPlayTime);
-            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
-            t.setAlpha(sc, alpha);
-        }
-
-        @Override
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
-            pw.print(" to="); pw.print(mToAlpha);
-            pw.print(" duration="); pw.println(mDuration);
-        }
-
-        @Override
-        public void dumpDebugInner(ProtoOutputStream proto) {
-            final long token = proto.start(ALPHA);
-            proto.write(FROM, mFromAlpha);
-            proto.write(TO, mToAlpha);
-            proto.write(DURATION_MS, mDuration);
-            proto.end(token);
-        }
-    }
+    abstract boolean updateDims(SurfaceControl.Transaction t);
 }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f81e5d4..f51bf7f 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -44,6 +44,7 @@
 import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -53,7 +54,6 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
-
 /**
  * Container for grouping WindowContainer below DisplayContent.
  *
@@ -79,6 +79,12 @@
     private final Configuration mTmpConfiguration = new Configuration();
 
     /**
+     * Prevent duplicate calls to onDisplayAreaAppeared, or early call of onDisplayAreaInfoChanged.
+     */
+    @VisibleForTesting
+    boolean mDisplayAreaAppearedSent;
+
+    /**
      * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it
      * can never specify orientation, but shows the fixed-orientation apps below it in the
      * letterbox; otherwise, it rotates based on the fixed-orientation request.
@@ -582,18 +588,31 @@
         sendDisplayAreaVanished(lastOrganizer);
         if (!skipDisplayAreaAppeared) {
             sendDisplayAreaAppeared();
+        } else if (organizer != null) {
+            // Set as sent since the DisplayAreaAppearedInfo will be sent back when registered.
+            mDisplayAreaAppearedSent = true;
         }
     }
 
+    @VisibleForTesting
     void sendDisplayAreaAppeared() {
-        if (mOrganizer == null) return;
+        if (mOrganizer == null || mDisplayAreaAppearedSent) return;
         mOrganizerController.onDisplayAreaAppeared(mOrganizer, this);
+        mDisplayAreaAppearedSent = true;
     }
 
+    @VisibleForTesting
+    void sendDisplayAreaInfoChanged() {
+        if (mOrganizer == null || !mDisplayAreaAppearedSent) return;
+        mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
+    }
+
+    @VisibleForTesting
     void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) {
-        if (organizer == null) return;
+        if (organizer == null || !mDisplayAreaAppearedSent) return;
         migrateToNewSurfaceControl(getSyncTransaction());
         mOrganizerController.onDisplayAreaVanished(organizer, this);
+        mDisplayAreaAppearedSent = false;
     }
 
     @Override
@@ -603,7 +622,7 @@
         super.onConfigurationChanged(newParentConfig);
 
         if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) {
-            mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
+            sendDisplayAreaInfoChanged();
         }
     }
 
@@ -766,7 +785,7 @@
      * DisplayArea that can be dimmed.
      */
     static class Dimmable extends DisplayArea<DisplayArea> {
-        private final Dimmer mDimmer = new Dimmer(this);
+        private final Dimmer mDimmer = Dimmer.create(this);
 
         Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
             super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b7b5c2af..f314900 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -937,6 +937,7 @@
                 // so we still request the window to resize if the current frame is empty.
                 if (!w.getFrame().isEmpty()) {
                     w.updateLastFrames();
+                    mWmService.mFrameChangingWindows.remove(w);
                 }
                 w.onResizeHandled();
             }
@@ -1621,7 +1622,17 @@
             return;
         }
 
+        final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting()
+                ? new Transition.ReadyCondition("displayConfig", this) : null;
+        if (displayConfig != null) {
+            mTransitionController.waitFor(displayConfig);
+        } else if (mTransitionController.isShellTransitionsEnabled()) {
+            Slog.e(TAG, "Display reconfigured outside of a transition: " + this);
+        }
         final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
+        if (displayConfig != null) {
+            displayConfig.meet();
+        }
         if (configUpdated) {
             return;
         }
@@ -4705,6 +4716,17 @@
             scheduleAnimation();
 
             mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+        } else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
+            // Even if the IME surface parent is not changed, the layer target belonging to the
+            // parent may have changes. Then attempt to reassign if the IME control target is
+            // possible to be the relative layer.
+            final SurfaceControl lastRelativeLayer = mImeWindowsContainer.getLastRelativeLayer();
+            if (lastRelativeLayer != mImeLayeringTarget.mSurfaceControl) {
+                assignRelativeLayerForIme(getSyncTransaction(), false /* forceUpdate */);
+                if (lastRelativeLayer != mImeWindowsContainer.getLastRelativeLayer()) {
+                    scheduleAnimation();
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 17bfeb4..d69c5ef 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1034,20 +1034,15 @@
                 }
                 break;
             case TYPE_NAVIGATION_BAR:
-                mContext.enforcePermission(
-                        android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
-                        "DisplayPolicy");
+                mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+                        callingPid, callingUid, "DisplayPolicy");
                 if (mNavigationBar != null && mNavigationBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
             case TYPE_NAVIGATION_BAR_PANEL:
-                // Check for permission if the caller is not the recents component.
-                if (!mService.mAtmService.isCallerRecents(callingUid)) {
-                    mContext.enforcePermission(
-                            android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
-                            "DisplayPolicy");
-                }
+                mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+                        callingPid, callingUid, "DisplayPolicy");
                 break;
             case TYPE_STATUS_BAR_ADDITIONAL:
             case TYPE_STATUS_BAR_SUB_PANEL:
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930d..1fa7d2a 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -51,7 +51,8 @@
     private final Rect mOldWindowCrop = new Rect();
 
     InputConsumerImpl(WindowManagerService service, IBinder token, String name,
-            InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) {
+            InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId,
+            SurfaceControl.Transaction t) {
         mService = service;
         mToken = token;
         mName = name;
@@ -82,6 +83,7 @@
                 .setName("Input Consumer " + name)
                 .setCallsite("InputConsumerImpl")
                 .build();
+        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
     }
 
     void linkToDeathRecipient() {
@@ -129,14 +131,12 @@
 
     void show(SurfaceControl.Transaction t, WindowContainer w) {
         t.show(mInputSurface);
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
     }
 
     void show(SurfaceControl.Transaction t, int layer) {
         t.show(mInputSurface);
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, layer);
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index af307ec3..5c0bc28 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -224,7 +224,7 @@
         }
 
         final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
-                inputChannel, clientPid, clientUser, mDisplayId);
+                inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction);
         switch (name) {
             case INPUT_CONSUMER_WALLPAPER:
                 consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER;
@@ -675,11 +675,6 @@
                     w.getKeyInterceptionInfo());
 
             if (w.mWinAnimator.hasSurface()) {
-                // Update trusted overlay changes here because they are tied to input info. Input
-                // changes can be updated even if surfaces aren't.
-                inputWindowHandle.setTrustedOverlay(mInputTransaction,
-                        w.mWinAnimator.mSurfaceController.mSurfaceControl,
-                        w.isWindowTrustedOverlay());
                 populateInputWindowHandle(inputWindowHandle, w);
                 setInputWindowInfoIfNeeded(mInputTransaction,
                         w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 02f5c21..cd114fc 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -68,6 +68,7 @@
     private final Rect mTmpRect = new Rect();
     private final InsetsStateController mStateController;
     private final InsetsSourceControl mFakeControl;
+    private final Consumer<Transaction> mSetLeashPositionConsumer;
     private @Nullable InsetsSourceControl mControl;
     private @Nullable InsetsControlTarget mControlTarget;
     private @Nullable InsetsControlTarget mPendingControlTarget;
@@ -85,16 +86,7 @@
     private boolean mInsetsHintStale = true;
     private @Flags int mFlagsFromFrameProvider;
     private @Flags int mFlagsFromServer;
-
-    private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
-        if (mControl != null) {
-            final SurfaceControl leash = mControl.getLeash();
-            if (leash != null) {
-                final Point position = mControl.getSurfacePosition();
-                t.setPosition(leash, position.x, position.y);
-            }
-        }
-    };
+    private boolean mHasPendingPosition;
 
     /** The visibility override from the current controlling window. */
     private boolean mClientVisible;
@@ -129,6 +121,21 @@
                 source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
                 new Point(), Insets.NONE);
         mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
+        mSetLeashPositionConsumer = t -> {
+            if (mControl != null) {
+                final SurfaceControl leash = mControl.getLeash();
+                if (leash != null) {
+                    final Point position = mControl.getSurfacePosition();
+                    t.setPosition(leash, position.x, position.y);
+                }
+            }
+            if (mHasPendingPosition) {
+                mHasPendingPosition = false;
+                if (mPendingControlTarget != mControlTarget) {
+                    mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
+                }
+            }
+        };
     }
 
     InsetsSource getSource() {
@@ -185,9 +192,8 @@
             mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
-                if (mPendingControlTarget != null) {
+                if (mPendingControlTarget != mControlTarget) {
                     updateControlForTarget(mPendingControlTarget, true /* force */);
-                    mPendingControlTarget = null;
                 }
             }
         }
@@ -344,6 +350,7 @@
                 changed = true;
                 if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
                         && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
+                    mHasPendingPosition = true;
                     windowState.applyWithNextDraw(mSetLeashPositionConsumer);
                 } else {
                     Transaction t = mWindowContainer.getSyncTransaction();
@@ -465,18 +472,23 @@
             // to control the window for now.
             return;
         }
+        mPendingControlTarget = target;
 
         if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) {
             // if window doesn't have a surface, set it null and return.
             setWindowContainer(null, null, null);
         }
         if (mWindowContainer == null) {
-            mPendingControlTarget = target;
             return;
         }
         if (target == mControlTarget && !force) {
             return;
         }
+        if (mHasPendingPosition) {
+            // Don't create a new leash while having a pending position. Otherwise, the position
+            // will be changed earlier than expected, which can cause flicker.
+            return;
+        }
         if (target == null) {
             // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
             mWindowContainer.cancelAnimation();
@@ -618,6 +630,7 @@
         }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
+        pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
         pw.println();
         if (mWindowContainer != null) {
             pw.print(prefix + "mWindowContainer=");
@@ -631,7 +644,7 @@
             pw.print(prefix + "mControlTarget=");
             pw.println(mControlTarget);
         }
-        if (mPendingControlTarget != null) {
+        if (mPendingControlTarget != mControlTarget) {
             pw.print(prefix + "mPendingControlTarget=");
             pw.println(mPendingControlTarget);
         }
@@ -652,7 +665,8 @@
         if (mControlTarget != null && mControlTarget.getWindow() != null) {
             mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
         }
-        if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) {
+        if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget
+                && mPendingControlTarget.getWindow() != null) {
             mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
         }
         if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 081ebe0..c4d0129 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -278,6 +278,12 @@
         notifyPendingInsetsControlChanged();
     }
 
+    void notifyControlTargetChanged(@Nullable InsetsControlTarget target,
+            InsetsSourceProvider provider) {
+        onControlTargetChanged(provider, target, false /* fake */);
+        notifyPendingInsetsControlChanged();
+    }
+
     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
         removeFromControlMaps(previousControlTarget, provider, false /* fake */);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1a319ad..fa2c94a 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -87,6 +87,7 @@
     private RootWindowContainer mRootWindowContainer;
     private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
     private boolean mWaitingForWakeTransition;
+    private Transition.ReadyCondition mWaitAodHide = null;
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityTaskSupervisor taskSupervisor) {
@@ -565,8 +566,14 @@
 
     // Defer transition until AOD dismissed.
     void updateDeferTransitionForAod(boolean waiting) {
-        if (waiting == mWaitingForWakeTransition) {
-            return;
+        if (mService.getTransitionController().useFullReadyTracking()) {
+            if (waiting == (mWaitAodHide != null)) {
+                return;
+            }
+        } else {
+            if (waiting == mWaitingForWakeTransition) {
+                return;
+            }
         }
         if (!mService.getTransitionController().isCollecting()) {
             return;
@@ -575,12 +582,17 @@
         if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
             mWaitingForWakeTransition = true;
             mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
+            mWaitAodHide = new Transition.ReadyCondition("AOD hidden");
+            mWindowManager.mAtmService.getTransitionController().waitFor(mWaitAodHide);
             mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
         } else if (!waiting) {
             // dismiss the deferring if the AOD state change or cancel awake.
             mWaitingForWakeTransition = false;
             mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
             mWindowManager.mH.removeCallbacks(mResetWaitTransition);
+            final Transition.ReadyCondition waitAodHide = mWaitAodHide;
+            mWaitAodHide = null;
+            waitAodHide.meet();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
new file mode 100644
index 0000000..3265e60
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyDimmer.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+public class LegacyDimmer extends Dimmer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    // This is in milliseconds.
+    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+    DimState mDimState;
+    private WindowContainer mLastRequestedDimContainer;
+    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+    private class DimAnimatable implements SurfaceAnimator.Animatable {
+        private SurfaceControl mDimLayer;
+
+        private DimAnimatable(SurfaceControl dimLayer) {
+            mDimLayer = dimLayer;
+        }
+
+        @Override
+        public SurfaceControl.Transaction getSyncTransaction() {
+            return mHost.getSyncTransaction();
+        }
+
+        @Override
+        public SurfaceControl.Transaction getPendingTransaction() {
+            return mHost.getPendingTransaction();
+        }
+
+        @Override
+        public void commitPendingTransaction() {
+            mHost.commitPendingTransaction();
+        }
+
+        @Override
+        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+        }
+
+        @Override
+        public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+        }
+
+        @Override
+        public SurfaceControl.Builder makeAnimationLeash() {
+            return mHost.makeAnimationLeash();
+        }
+
+        @Override
+        public SurfaceControl getAnimationLeashParent() {
+            return mHost.getSurfaceControl();
+        }
+
+        @Override
+        public SurfaceControl getSurfaceControl() {
+            return mDimLayer;
+        }
+
+        @Override
+        public SurfaceControl getParentSurfaceControl() {
+            return mHost.getSurfaceControl();
+        }
+
+        @Override
+        public int getSurfaceWidth() {
+            // This will determine the size of the leash created. This should be the size of the
+            // host and not the dim layer since the dim layer may get bigger during animation. If
+            // that occurs, the leash size cannot change so we need to ensure the leash is big
+            // enough that the dim layer can grow.
+            // This works because the mHost will be a Task which has the display bounds.
+            return mHost.getSurfaceWidth();
+        }
+
+        @Override
+        public int getSurfaceHeight() {
+            // See getSurfaceWidth() above for explanation.
+            return mHost.getSurfaceHeight();
+        }
+
+        void removeSurface() {
+            if (mDimLayer != null && mDimLayer.isValid()) {
+                getSyncTransaction().remove(mDimLayer);
+            }
+            mDimLayer = null;
+        }
+    }
+
+    @VisibleForTesting
+    class DimState {
+        /**
+         * The layer where property changes should be invoked on.
+         */
+        SurfaceControl mDimLayer;
+        boolean mDimming;
+        boolean mIsVisible;
+
+        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+        final Rect mDimBounds = new Rect();
+
+        /**
+         * Determines whether the dim layer should animate before destroying.
+         */
+        boolean mAnimateExit = true;
+
+        /**
+         * Used for Dims not associated with a WindowContainer.
+         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
+         * lifecycle.
+         */
+        boolean mDontReset;
+        SurfaceAnimator mSurfaceAnimator;
+
+        DimState(SurfaceControl dimLayer) {
+            mDimLayer = dimLayer;
+            mDimming = true;
+            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
+            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
+                if (!mDimming) {
+                    dimAnimatable.removeSurface();
+                }
+            }, mHost.mWmService);
+        }
+    }
+
+    @VisibleForTesting
+    interface SurfaceAnimatorStarter {
+        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
+    }
+
+    protected LegacyDimmer(WindowContainer host) {
+        this(host, SurfaceAnimator::startAnimation);
+    }
+
+    LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+        super(host);
+        mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+    }
+
+    private DimState obtainDimState(WindowContainer container) {
+        if (mDimState == null) {
+            try {
+                final SurfaceControl ctl = makeDimLayer();
+                mDimState = new DimState(ctl);
+            } catch (Surface.OutOfResourcesException e) {
+                Log.w(TAG, "OutOfResourcesException creating dim surface");
+            }
+        }
+
+        mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    private SurfaceControl makeDimLayer() {
+        return mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer()
+                .setName("Dim Layer for - " + mHost.getName())
+                .setCallsite("Dimmer.makeDimLayer")
+                .build();
+    }
+
+    @Override
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimLayer : null;
+    }
+
+    @Override
+    void resetDimStates() {
+        if (mDimState == null) {
+            return;
+        }
+        if (!mDimState.mDontReset) {
+            mDimState.mDimming = false;
+        }
+    }
+
+    @Override
+    Rect getDimBounds() {
+        return mDimState != null ? mDimState.mDimBounds : null;
+    }
+
+    @Override
+    void dontAnimateExit() {
+        if (mDimState != null) {
+            mDimState.mAnimateExit = false;
+        }
+    }
+
+    @Override
+    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+        final DimState d = obtainDimState(container);
+        if (d == null) {
+            return;
+        }
+
+        // The dim method is called from WindowState.prepareSurfaces(), which is always called
+        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+        // relative to the highest Z layer with a dim.
+        SurfaceControl.Transaction t = mHost.getPendingTransaction();
+        t.setAlpha(d.mDimLayer, alpha);
+        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
+        d.mDimming = true;
+    }
+
+    @Override
+    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+        final DimState d = mDimState;
+        if (d != null) {
+            SurfaceControl.Transaction t = mHost.getPendingTransaction();
+            t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
+        }
+    }
+
+    @Override
+    boolean updateDims(SurfaceControl.Transaction t) {
+        if (mDimState == null) {
+            return false;
+        }
+
+        if (!mDimState.mDimming) {
+            if (!mDimState.mAnimateExit) {
+                if (mDimState.mDimLayer.isValid()) {
+                    t.remove(mDimState.mDimLayer);
+                }
+            } else {
+                startDimExit(mLastRequestedDimContainer,
+                        mDimState.mSurfaceAnimator, t);
+            }
+            mDimState = null;
+            return false;
+        } else {
+            final Rect bounds = mDimState.mDimBounds;
+            // TODO: Once we use geometry from hierarchy this falls away.
+            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+            if (!mDimState.mIsVisible) {
+                mDimState.mIsVisible = true;
+                t.show(mDimState.mDimLayer);
+                // Skip enter animation while starting window is on top of its activity
+                final WindowState ws = mLastRequestedDimContainer.asWindowState();
+                if (ws == null || ws.mActivityRecord == null
+                        || ws.mActivityRecord.mStartingData == null) {
+                    startDimEnter(mLastRequestedDimContainer,
+                            mDimState.mSurfaceAnimator, t);
+                }
+            }
+            return true;
+        }
+    }
+
+    private long getDimDuration(WindowContainer container) {
+        // Use the same duration as the animation on the WindowContainer
+        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+                : animationAdapter.getDurationHint();
+    }
+
+    private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
+            SurfaceControl.Transaction t) {
+        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
+    }
+
+    private void startDimExit(WindowContainer container, SurfaceAnimator animator,
+            SurfaceControl.Transaction t) {
+        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
+    }
+
+    private void startAnim(WindowContainer container, SurfaceAnimator animator,
+            SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
+        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
+                        new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
+                        mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+                ANIMATION_TYPE_DIMMER);
+    }
+
+    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+        private final long mDuration;
+        private final float mFromAlpha;
+        private final float mToAlpha;
+
+        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
+            mFromAlpha = fromAlpha;
+            mToAlpha = toAlpha;
+            mDuration = duration;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+            final float fraction = getFraction(currentPlayTime);
+            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
+            t.setAlpha(sc, alpha);
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
+            pw.print(" to="); pw.print(mToAlpha);
+            pw.print(" duration="); pw.println(mDuration);
+        }
+
+        @Override
+        public void dumpDebugInner(ProtoOutputStream proto) {
+            final long token = proto.start(ALPHA);
+            proto.write(FROM, mFromAlpha);
+            proto.write(TO, mToAlpha);
+            proto.write(DURATION_MS, mDuration);
+            proto.end(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index f9fa9e6..f6aad4c 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,7 +36,10 @@
 
 import com.android.server.UiThread;
 
+import java.util.function.BooleanSupplier;
+import java.util.function.DoubleSupplier;
 import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 /**
@@ -50,12 +53,12 @@
 
     private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
-    private final Supplier<Boolean> mAreCornersRounded;
+    private final BooleanSupplier mAreCornersRounded;
     private final Supplier<Color> mColorSupplier;
     // Parameters for "blurred wallpaper" letterbox background.
-    private final Supplier<Boolean> mHasWallpaperBackgroundSupplier;
-    private final Supplier<Integer> mBlurRadiusSupplier;
-    private final Supplier<Float> mDarkScrimAlphaSupplier;
+    private final BooleanSupplier mHasWallpaperBackgroundSupplier;
+    private final IntSupplier mBlurRadiusSupplier;
+    private final DoubleSupplier mDarkScrimAlphaSupplier;
     private final Supplier<SurfaceControl> mParentSurfaceSupplier;
 
     private final Rect mOuter = new Rect();
@@ -82,11 +85,11 @@
      */
     public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            Supplier<Boolean> areCornersRounded,
+            BooleanSupplier areCornersRounded,
             Supplier<Color> colorSupplier,
-            Supplier<Boolean> hasWallpaperBackgroundSupplier,
-            Supplier<Integer> blurRadiusSupplier,
-            Supplier<Float> darkScrimAlphaSupplier,
+            BooleanSupplier hasWallpaperBackgroundSupplier,
+            IntSupplier blurRadiusSupplier,
+            DoubleSupplier darkScrimAlphaSupplier,
             IntConsumer doubleTapCallbackX,
             IntConsumer doubleTapCallbackY,
             Supplier<SurfaceControl> parentSurface) {
@@ -247,7 +250,7 @@
      * Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
      */
     private boolean useFullWindowSurface() {
-        return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
+        return mAreCornersRounded.getAsBoolean() || mHasWallpaperBackgroundSupplier.getAsBoolean();
     }
 
     private final class TapEventReceiver extends InputEventReceiver {
@@ -424,7 +427,7 @@
                         mSurfaceFrameRelative.height());
                 t.reparent(mSurface, mParentSurface);
 
-                mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
+                mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.getAsBoolean();
                 updateAlphaAndBlur(t);
 
                 t.show(mSurface);
@@ -445,17 +448,17 @@
                 t.setBackgroundBlurRadius(mSurface, 0);
                 return;
             }
-            final float alpha = mDarkScrimAlphaSupplier.get();
+            final float alpha = (float) mDarkScrimAlphaSupplier.getAsDouble();
             t.setAlpha(mSurface, alpha);
 
             // Translucent dark scrim can be shown without blur.
-            if (mBlurRadiusSupplier.get() <= 0) {
+            if (mBlurRadiusSupplier.getAsInt() <= 0) {
                 // Removing pre-exesting blur
                 t.setBackgroundBlurRadius(mSurface, 0);
                 return;
             }
 
-            t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.get());
+            t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.getAsInt());
         }
 
         private float[] getRgbColorArray() {
@@ -472,7 +475,7 @@
                     // and mParentSurface may never be updated in applySurfaceChanges but this
                     // doesn't mean that update is needed.
                     || !mSurfaceFrameRelative.isEmpty()
-                    && (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground
+                    && (mHasWallpaperBackgroundSupplier.getAsBoolean() != mHasWallpaperBackground
                     || !mColorSupplier.get().equals(mColor)
                     || mParentSurfaceSupplier.get() != mParentSurface);
         }
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 458786f..f8c39d0 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -16,6 +16,7 @@
 mariiasand@google.com
 rgl@google.com
 yunfanc@google.com
+wilsonshih@google.com
 
 per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
\ No newline at end of file
+per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 99831d3..23c135a 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,6 +19,8 @@
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
 
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
+
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.Display.Mode;
@@ -137,7 +139,7 @@
         // to run in default refresh rate. But if the display size of default mode is different
         // from the using preferred mode, then still keep the preferred mode to avoid disturbing
         // the animation.
-        if (w.isAnimationRunningSelfOrParent()) {
+        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
             Display.Mode preferredMode = null;
             for (Display.Mode mode : mDisplayInfo.supportedModes) {
                 if (preferredDisplayModeId == mode.getModeId()) {
@@ -251,7 +253,7 @@
 
         // If app is animating, it's not able to control refresh rate because we want the animation
         // to run in default refresh rate.
-        if (w.isAnimationRunningSelfOrParent()) {
+        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
             return w.mFrameRateVote.reset();
         }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf6a1fe..c81105a 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,6 +847,7 @@
         }
 
         handleResizingWindows();
+        clearFrameChangingWindows();
 
         if (mWmService.mDisplayFrozen) {
             ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -1015,6 +1016,17 @@
     }
 
     /**
+     * Clears frame changing windows after handling moving and resizing windows.
+     */
+    private void clearFrameChangingWindows() {
+        final ArrayList<WindowState> frameChangingWindows = mWmService.mFrameChangingWindows;
+        for (int i = frameChangingWindows.size() - 1; i >= 0; i--) {
+            frameChangingWindows.get(i).updateLastFrames();
+        }
+        frameChangingWindows.clear();
+    }
+
+    /**
      * @param w        WindowState this method is applied to.
      * @param obscured True if there is a window on top of this obscuring the display.
      * @param syswin   System window?
@@ -2039,6 +2051,8 @@
         }
 
         transitionController.deferTransitionReady();
+        Transition.ReadyCondition pipChangesApplied = new Transition.ReadyCondition("movedToPip");
+        transitionController.waitFor(pipChangesApplied);
         mService.deferWindowLayout();
         try {
             // This will change the root pinned task's windowing mode to its original mode, ensuring
@@ -2211,6 +2225,10 @@
                 mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
                         organizedTf);
             }
+
+            if (taskDisplayArea.getFocusedRootTask() == rootTask) {
+                taskDisplayArea.clearPreferredTopFocusableRootTask();
+            }
         } finally {
             mService.continueWindowLayout();
             try {
@@ -2219,6 +2237,7 @@
                 }
             } finally {
                 transitionController.continueTransitionReady();
+                pipChangesApplied.meet();
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
new file mode 100644
index 0000000..2549bbf
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+class SmoothDimmer extends Dimmer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    private static final float EPSILON = 0.0001f;
+    // This is in milliseconds.
+    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+    DimState mDimState;
+    private WindowContainer mLastRequestedDimContainer;
+    private final AnimationAdapterFactory mAnimationAdapterFactory;
+
+    @VisibleForTesting
+    class DimState {
+        /**
+         * The layer where property changes should be invoked on.
+         */
+        SurfaceControl mDimLayer;
+        boolean mDimming;
+        boolean mIsVisible;
+
+        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+        final Rect mDimBounds = new Rect();
+
+        /**
+         * Determines whether the dim layer should animate before destroying.
+         */
+        boolean mAnimateExit = true;
+
+        /**
+         * Used for Dims not associated with a WindowContainer.
+         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
+         * lifecycle.
+         */
+        boolean mDontReset;
+
+        Change mCurrentProperties;
+        Change mRequestedProperties;
+        private AnimationSpec mAlphaAnimationSpec;
+        private AnimationAdapter mLocalAnimationAdapter;
+
+        static class Change {
+            private float mAlpha = -1f;
+            private int mBlurRadius = -1;
+            private WindowContainer mDimmingContainer = null;
+            private int mRelativeLayer = -1;
+            private boolean mSkipAnimation = false;
+
+            Change() {}
+
+            Change(Change other) {
+                mAlpha = other.mAlpha;
+                mBlurRadius = other.mBlurRadius;
+                mDimmingContainer = other.mDimmingContainer;
+                mRelativeLayer = other.mRelativeLayer;
+            }
+
+            @Override
+            public String toString() {
+                return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+                        + mDimmingContainer + ", relativePosition=" + mRelativeLayer
+                        + ", skipAnimation=" + mSkipAnimation;
+            }
+        }
+
+        DimState(SurfaceControl dimLayer) {
+            mDimLayer = dimLayer;
+            mDimming = true;
+            mCurrentProperties = new Change();
+            mRequestedProperties = new Change();
+        }
+
+        void setExitParameters(WindowContainer container) {
+            setRequestedRelativeParent(container, -1 /* relativeLayer */);
+            setRequestedAppearance(0f /* alpha */, 0 /* blur */);
+        }
+
+        // Sets a requested change without applying it immediately
+        void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+            mRequestedProperties.mDimmingContainer = relativeParent;
+            mRequestedProperties.mRelativeLayer = relativeLayer;
+        }
+
+        // Sets a requested change without applying it immediately
+        void setRequestedAppearance(float alpha, int blurRadius) {
+            mRequestedProperties.mAlpha = alpha;
+            mRequestedProperties.mBlurRadius = blurRadius;
+        }
+
+        /**
+         * Commit the last changes we received. Called after
+         * {@link Change#setExitParameters(WindowContainer)},
+         * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
+         * {@link Change#setRequestedAppearance(float, int)}
+         */
+        void applyChanges(SurfaceControl.Transaction t) {
+            if (mRequestedProperties.mDimmingContainer == null) {
+                Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
+                        + "call adjustRelativeLayer?");
+                return;
+            }
+            if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+                Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+                        + "does not have a surface");
+                return;
+            }
+            if (!mDimState.mIsVisible) {
+                mDimState.mIsVisible = true;
+                t.show(mDimState.mDimLayer);
+            }
+            t.setRelativeLayer(mDimLayer,
+                    mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+                    mRequestedProperties.mRelativeLayer);
+
+            if (aspectChanged()) {
+                if (isAnimating()) {
+                    mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
+                }
+                if (mRequestedProperties.mSkipAnimation
+                        || (!dimmingContainerChanged() && mDimming)) {
+                    // If the dimming container has not changed, then it is running its own
+                    // animation, thus we can directly set the values we get requested, unless it's
+                    // the exiting animation
+                    ProtoLog.d(WM_DEBUG_DIMMER,
+                            "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+                            mDimLayer, mRequestedProperties.mAlpha,
+                            mRequestedProperties.mBlurRadius);
+                    t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+                    t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+                    mRequestedProperties.mSkipAnimation = false;
+                } else {
+                    startAnimation(t);
+                }
+            }
+            mCurrentProperties = new Change(mRequestedProperties);
+        }
+
+        private void startAnimation(SurfaceControl.Transaction t) {
+            mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
+                    mRequestedProperties.mBlurRadius);
+            mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+                    mHost.mWmService.mSurfaceAnimationRunner);
+
+            mLocalAnimationAdapter.startAnimation(mDimLayer, t,
+                    ANIMATION_TYPE_DIMMER, (type, animator) -> {
+                        t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+                        t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+                        if (mRequestedProperties.mAlpha == 0f && !mDimming) {
+                            ProtoLog.d(WM_DEBUG_DIMMER,
+                                    "Removing dim surface %s on transaction %s", mDimLayer, t);
+                            t.remove(mDimLayer);
+                        }
+                        mLocalAnimationAdapter = null;
+                        mAlphaAnimationSpec = null;
+                    });
+        }
+
+        private boolean isAnimating() {
+            return mAlphaAnimationSpec != null;
+        }
+
+        private boolean aspectChanged() {
+            return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
+                    || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
+        }
+
+        private boolean dimmingContainerChanged() {
+            return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
+        }
+
+        private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
+            final float startAlpha;
+            final int startBlur;
+            if (mAlphaAnimationSpec != null) {
+                startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
+                startBlur = mAlphaAnimationSpec.mCurrentBlur;
+            } else {
+                startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+                startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+            }
+            long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+                    * Math.abs(targetAlpha - startAlpha));
+
+            ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
+                            + "alpha: %f -> %f, blur: %d -> %d",
+                    mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
+                    startBlur, targetBlur);
+            return new AnimationSpec(
+                    new AnimationExtremes<>(startAlpha, targetAlpha),
+                    new AnimationExtremes<>(startBlur, targetBlur),
+                    duration
+            );
+        }
+    }
+
+    protected SmoothDimmer(WindowContainer host) {
+        this(host, new AnimationAdapterFactory());
+    }
+
+    @VisibleForTesting
+    SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
+        super(host);
+        mAnimationAdapterFactory = animationFactory;
+    }
+
+    private DimState obtainDimState(WindowContainer container) {
+        if (mDimState == null) {
+            try {
+                final SurfaceControl ctl = makeDimLayer();
+                mDimState = new DimState(ctl);
+            } catch (Surface.OutOfResourcesException e) {
+                Log.w(TAG, "OutOfResourcesException creating dim surface");
+            }
+        }
+
+        mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    private SurfaceControl makeDimLayer() {
+        return mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer()
+                .setName("Dim Layer for - " + mHost.getName())
+                .setCallsite("Dimmer.makeDimLayer")
+                .build();
+    }
+
+    @Override
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimLayer : null;
+    }
+
+    @Override
+    void resetDimStates() {
+        if (mDimState == null) {
+            return;
+        }
+        if (!mDimState.mDontReset) {
+            mDimState.mDimming = false;
+        }
+    }
+
+    @Override
+    Rect getDimBounds() {
+        return mDimState != null ? mDimState.mDimBounds : null;
+    }
+
+    @Override
+    void dontAnimateExit() {
+        if (mDimState != null) {
+            mDimState.mAnimateExit = false;
+        }
+    }
+
+    @Override
+    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+        final DimState d = obtainDimState(container);
+        mDimState.setRequestedAppearance(alpha, blurRadius);
+        d.mDimming = true;
+    }
+
+    @Override
+    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+        if (mDimState != null) {
+            mDimState.setRequestedRelativeParent(container, relativeLayer);
+        }
+    }
+
+    boolean updateDims(SurfaceControl.Transaction t) {
+        if (mDimState == null) {
+            return false;
+        }
+
+        if (!mDimState.mDimming) {
+            // No one is dimming anymore, fade out dim and remove
+            if (!mDimState.mAnimateExit) {
+                if (mDimState.mDimLayer.isValid()) {
+                    t.remove(mDimState.mDimLayer);
+                }
+            } else {
+                mDimState.setExitParameters(
+                        mDimState.mRequestedProperties.mDimmingContainer);
+                mDimState.applyChanges(t);
+            }
+            mDimState = null;
+            return false;
+        }
+        final Rect bounds = mDimState.mDimBounds;
+        // TODO: Once we use geometry from hierarchy this falls away.
+        t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+        t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+        // Skip enter animation while starting window is on top of its activity
+        final WindowState ws = mLastRequestedDimContainer.asWindowState();
+        if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+                && ws.mActivityRecord.mStartingData != null) {
+            mDimState.mRequestedProperties.mSkipAnimation = true;
+        }
+        mDimState.applyChanges(t);
+        return true;
+    }
+
+    private long getDimDuration(WindowContainer container) {
+        // Use the same duration as the animation on the WindowContainer
+        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+                : animationAdapter.getDurationHint();
+    }
+
+    private static class AnimationExtremes<T> {
+        final T mStartValue;
+        final T mFinishValue;
+
+        AnimationExtremes(T fromValue, T toValue) {
+            mStartValue = fromValue;
+            mFinishValue = toValue;
+        }
+    }
+
+    private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+        private final long mDuration;
+        private final AnimationExtremes<Float> mAlpha;
+        private final AnimationExtremes<Integer> mBlur;
+
+        float mCurrentAlpha = 0;
+        int mCurrentBlur = 0;
+
+        AnimationSpec(AnimationExtremes<Float> alpha,
+                AnimationExtremes<Integer> blur, long duration) {
+            mAlpha = alpha;
+            mBlur = blur;
+            mDuration = duration;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+            final float fraction = getFraction(currentPlayTime);
+            mCurrentAlpha =
+                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+            mCurrentBlur =
+                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+            t.setAlpha(sc, mCurrentAlpha);
+            t.setBackgroundBlurRadius(sc, mCurrentBlur);
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+            pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+            pw.print(" duration="); pw.println(mDuration);
+        }
+
+        @Override
+        public void dumpDebugInner(ProtoOutputStream proto) {
+            final long token = proto.start(ALPHA);
+            proto.write(FROM, mAlpha.mStartValue);
+            proto.write(TO, mAlpha.mFinishValue);
+            proto.write(DURATION_MS, mDuration);
+            proto.end(token);
+        }
+    }
+
+    static class AnimationAdapterFactory {
+
+        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+                SurfaceAnimationRunner runner) {
+            return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 2e7ff7a..2be2a1a 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -26,6 +26,7 @@
 
 import android.os.Trace;
 import android.view.WindowManager;
+import android.window.TaskSnapshot;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -85,7 +86,7 @@
             if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
             if (info.mContainer.isActivityTypeHome()) continue;
             final Task task = info.mContainer.asTask();
-            if (task != null && !task.isVisibleRequested()) {
+            if (task != null && !task.mCreatedByOrganizer && !task.isVisibleRequested()) {
                 mTaskSnapshotController.recordSnapshot(task, info);
             }
             // Won't need to capture activity snapshot in close transition.
@@ -126,6 +127,18 @@
         }
         mActivitySnapshotController.handleTransitionFinish(windows);
         mActivitySnapshotController.endSnapshotProcess();
+        // Remove task snapshot if it is visible at the end of transition.
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = changeInfos.get(i).mContainer;
+            final Task task = wc.asTask();
+            if (task != null && wc.isVisibleRequested()) {
+                final TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(task.mTaskId,
+                        task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */);
+                if (snapshot != null) {
+                    mTaskSnapshotController.removeAndDeleteSnapshot(task.mTaskId, task.mUserId);
+                }
+            }
+        }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 408ea6e..c3de4d5 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -49,7 +49,8 @@
  * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
  * animation will be invoked, at which we reparent the children back to the original parent.
  */
-class SurfaceAnimator {
+@VisibleForTesting
+public class SurfaceAnimator {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
 
@@ -617,7 +618,8 @@
      * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
      * component that is running the animation when the animation is finished.
      */
-    interface OnAnimationFinishedCallback {
+    @VisibleForTesting
+    public interface OnAnimationFinishedCallback {
         void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8385615..fa104bb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -240,7 +240,6 @@
     private static final String ATTR_ROOT_AFFINITY = "root_affinity";
     private static final String ATTR_ROOTHASRESET = "root_has_reset";
     private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents";
-    private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
     private static final String ATTR_USERID = "user_id";
     private static final String ATTR_USER_SETUP_COMPLETE = "user_setup_complete";
     private static final String ATTR_EFFECTIVE_UID = "effective_uid";
@@ -343,7 +342,6 @@
                             // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
     boolean autoRemoveRecents;  // If true, we should automatically remove the task from
                                 // recents when activity finishes
-    boolean askedCompatMode;// Have asked the user about compat mode for this task.
     private boolean mHasBeenVisible; // Set if any activities in the task have been visible
 
     String stringName;      // caching of toString() result.
@@ -488,10 +486,6 @@
 
     private boolean mForceShowForAllUsers;
 
-    /** 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;
-    private int mForceHiddenFlags = 0;
     private boolean mForceTranslucent = false;
 
     // The display category name for this task.
@@ -620,7 +614,7 @@
     private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
-            boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
+            boolean _autoRemoveRecents, int _userId, int _effectiveUid,
             String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
             TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
             int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
@@ -655,7 +649,6 @@
         rootWasReset = _rootWasReset;
         isAvailable = true;
         autoRemoveRecents = _autoRemoveRecents;
-        askedCompatMode = _askedCompatMode;
         mUserSetupComplete = userSetupComplete;
         effectiveUid = _effectiveUid;
         touchActiveTime();
@@ -1336,7 +1329,7 @@
 
         clearRootProcess();
 
-        mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
+        mAtmService.mWindowManager.mTaskSnapshotController.removeAndDeleteSnapshot(
                 mTaskId, mUserId);
     }
 
@@ -2858,7 +2851,8 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    void getDimBounds(Rect out) {
+    @Override
+    void getDimBounds(@NonNull Rect out) {
         if (isRootTask()) {
             getBounds(out);
             return;
@@ -3306,7 +3300,8 @@
         // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
         // If true, we want to get the Dimmer from the level above since we don't want to animate
         // the dim with the Task.
-        if (!isRootTask() || isTranslucent(null)) {
+        if (!isRootTask() || (Dimmer.DIMMER_REFACTOR && isTranslucentAndVisible())
+                || isTranslucent(null)) {
             return super.getDimmer();
         }
 
@@ -3648,7 +3643,7 @@
      */
     TaskFragmentParentInfo getTaskFragmentParentInfo() {
         return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
-                shouldBeVisible(null /* starting */));
+                shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity());
     }
 
     @Override
@@ -3769,8 +3764,8 @@
             pw.println(")");
         }
         pw.print(prefix); pw.print("Activities="); pw.println(mChildren);
-        if (!askedCompatMode || !inRecents || !isAvailable) {
-            pw.print(prefix); pw.print("askedCompatMode="); pw.print(askedCompatMode);
+        if (!inRecents || !isAvailable) {
+            pw.print(prefix);
             pw.print(" inRecents="); pw.print(inRecents);
             pw.print(" isAvailable="); pw.println(isAvailable);
         }
@@ -3878,7 +3873,6 @@
         }
         out.attributeBoolean(null, ATTR_ROOTHASRESET, rootWasReset);
         out.attributeBoolean(null, ATTR_AUTOREMOVERECENTS, autoRemoveRecents);
-        out.attributeBoolean(null, ATTR_ASKEDCOMPATMODE, askedCompatMode);
         out.attributeInt(null, ATTR_USERID, mUserId);
         out.attributeBoolean(null, ATTR_USER_SETUP_COMPLETE, mUserSetupComplete);
         out.attributeInt(null, ATTR_EFFECTIVE_UID, effectiveUid);
@@ -3976,7 +3970,6 @@
         String windowLayoutAffinity = null;
         boolean rootHasReset = false;
         boolean autoRemoveRecents = false;
-        boolean askedCompatMode = false;
         int taskType = 0;
         int userId = 0;
         boolean userSetupComplete = true;
@@ -4037,9 +4030,6 @@
                 case ATTR_AUTOREMOVERECENTS:
                     autoRemoveRecents = Boolean.parseBoolean(attrValue);
                     break;
-                case ATTR_ASKEDCOMPATMODE:
-                    askedCompatMode = Boolean.parseBoolean(attrValue);
-                    break;
                 case ATTR_USERID:
                     userId = Integer.parseInt(attrValue);
                     break;
@@ -4193,7 +4183,6 @@
                 .setOrigActivity(origActivity)
                 .setRootWasReset(rootHasReset)
                 .setAutoRemoveRecents(autoRemoveRecents)
-                .setAskedCompatMode(askedCompatMode)
                 .setUserId(userId)
                 .setEffectiveUid(effectiveUid)
                 .setLastDescription(lastDescription)
@@ -4492,20 +4481,13 @@
      * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
      * @return Whether the force hidden state changed
      */
-    boolean setForceHidden(int flags, boolean set) {
-        int newFlags = mForceHiddenFlags;
-        if (set) {
-            newFlags |= flags;
-        } else {
-            newFlags &= ~flags;
-        }
-        if (mForceHiddenFlags == newFlags) {
-            return false;
-        }
-
+    @Override
+    boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
         final boolean wasHidden = isForceHidden();
         final boolean wasVisible = isVisible();
-        mForceHiddenFlags = newFlags;
+        if (!super.setForceHidden(flags, set)) {
+            return false;
+        }
         final boolean nowHidden = isForceHidden();
         if (wasHidden != nowHidden) {
             final String reason = "setForceHidden";
@@ -4536,11 +4518,6 @@
         return super.isAlwaysOnTop();
     }
 
-    @Override
-    protected boolean isForceHidden() {
-        return mForceHiddenFlags != 0;
-    }
-
     boolean isForceHiddenForPinnedTask() {
         return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
     }
@@ -4738,7 +4715,7 @@
         }
         if (isAttached()) {
             setWindowingMode(WINDOWING_MODE_UNDEFINED);
-            moveTaskToBackInner(this);
+            moveTaskToBackInner(this, null /* transition */);
         }
         if (top.isAttached()) {
             top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
@@ -5648,6 +5625,8 @@
             if (noAnimation) {
                 mDisplayContent.prepareAppTransition(TRANSIT_NONE);
                 mTaskSupervisor.mNoAnimActivities.add(top);
+                mTransitionController.collect(top);
+                mTransitionController.setNoAnimation(top);
                 ActivityOptions.abort(options);
             } else {
                 updateTransitLocked(TRANSIT_TO_FRONT, options);
@@ -5739,24 +5718,27 @@
                         mTransitionController.requestStartTransition(transition, tr,
                                 null /* remoteTransition */, null /* displayChange */);
                         mTransitionController.collect(tr);
-                        moveTaskToBackInner(tr);
+                        moveTaskToBackInner(tr, transition);
                     });
         } else {
             // Skip the transition for pinned task.
             if (!inPinnedWindowingMode()) {
                 mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
             }
-            moveTaskToBackInner(tr);
+            moveTaskToBackInner(tr, null /* transition */);
         }
         return true;
     }
 
-    private boolean moveTaskToBackInner(@NonNull Task task) {
-        if (mTransitionController.isShellTransitionsEnabled()) {
+    private boolean moveTaskToBackInner(@NonNull Task task, @Nullable Transition transition) {
+        final Transition.ReadyCondition movedToBack =
+                new Transition.ReadyCondition("moved-to-back", task);
+        if (transition != null) {
             // Preventing from update surface position for WindowState if configuration changed,
             // because the position is depends on WindowFrame, so update the position before
             // relayout will only update it to "old" position.
             mAtmService.deferWindowLayout();
+            transition.mReadyTracker.add(movedToBack);
         }
         try {
             moveToBack("moveTaskToBackInner", task);
@@ -5773,6 +5755,9 @@
             if (mTransitionController.isShellTransitionsEnabled()) {
                 mAtmService.continueWindowLayout();
             }
+            if (transition != null) {
+                movedToBack.meet();
+            }
         }
         ActivityRecord topActivity = getDisplayArea().topRunningActivity();
         Task topRootTask = topActivity == null ? null : topActivity.getRootTask();
@@ -6077,6 +6062,11 @@
             // Non-root task position changed.
             mRootWindowContainer.invalidateTaskLayers();
         }
+
+        if (child.asActivityRecord() != null) {
+            // Send for TaskFragmentParentInfo#hasDirectActivity change.
+            sendTaskFragmentParentInfoChangedIfNeeded();
+        }
     }
 
     void reparent(TaskDisplayArea newParent, boolean onTop) {
@@ -6280,7 +6270,6 @@
         private ComponentName mOrigActivity;
         private boolean mRootWasReset;
         private boolean mAutoRemoveRecents;
-        private boolean mAskedCompatMode;
         private int mUserId;
         private int mEffectiveUid;
         private String mLastDescription;
@@ -6535,11 +6524,6 @@
             return this;
         }
 
-        private Builder setAskedCompatMode(boolean askedCompatMode) {
-            mAskedCompatMode = askedCompatMode;
-            return this;
-        }
-
         private Builder setAffinityIntent(Intent affinityIntent) {
             mAffinityIntent = affinityIntent;
             return this;
@@ -6672,7 +6656,7 @@
         Task buildInner() {
             return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
                     mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
-                    mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
+                    mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
                     mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
                     mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
                     mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825..2fc531a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -209,7 +209,26 @@
      */
     int mMinHeight;
 
-    Dimmer mDimmer = new Dimmer(this);
+    Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
+            ? new SmoothDimmer(this) : new LegacyDimmer(this);
+
+    /** Apply the dim layer on the embedded TaskFragment. */
+    static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
+
+    /** Apply the dim layer on the parent Task for an embedded TaskFragment. */
+    static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1;
+
+    /**
+     * The type of dim layer area for an embedded TaskFragment.
+     */
+    @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = {
+            EMBEDDED_DIM_AREA_TASK_FRAGMENT,
+            EMBEDDED_DIM_AREA_PARENT_TASK,
+    })
+    @interface EmbeddedDimArea {}
+
+    @EmbeddedDimArea
+    private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;
 
     /** This task fragment will be removed when the cleanup of its children are done. */
     private boolean mIsRemovalRequested;
@@ -342,6 +361,19 @@
      */
     private boolean mIsolatedNav;
 
+    /** 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;
+    static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2;
+
+    @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = {
+            FLAG_FORCE_HIDDEN_FOR_PINNED_TASK,
+            FLAG_FORCE_HIDDEN_FOR_TASK_ORG,
+            FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG,
+    }, flag = true)
+    @interface FlagForceHidden {}
+    protected int mForceHiddenFlags = 0;
+
     final Point mLastSurfaceSize = new Point();
 
     private final Rect mTmpBounds = new Rect();
@@ -358,41 +390,6 @@
 
     private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
             new EnsureActivitiesVisibleHelper(this);
-    private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
-            new EnsureVisibleActivitiesConfigHelper();
-    private class EnsureVisibleActivitiesConfigHelper implements Predicate<ActivityRecord> {
-        private boolean mUpdateConfig;
-        private boolean mPreserveWindow;
-        private boolean mBehindFullscreen;
-
-        void reset(boolean preserveWindow) {
-            mPreserveWindow = preserveWindow;
-            mUpdateConfig = false;
-            mBehindFullscreen = false;
-        }
-
-        void process(ActivityRecord start, boolean preserveWindow) {
-            if (start == null || !start.isVisibleRequested()) {
-                return;
-            }
-            reset(preserveWindow);
-            forAllActivities(this, start, true /* includeBoundary */,
-                    true /* traverseTopToBottom */);
-
-            if (mUpdateConfig) {
-                // Ensure the resumed state of the focus activity if we updated the configuration of
-                // any activity.
-                mRootWindowContainer.resumeFocusedTasksTopActivities();
-            }
-        }
-
-        @Override
-        public boolean test(ActivityRecord r) {
-            mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
-            mBehindFullscreen |= r.occludesParent();
-            return mBehindFullscreen;
-        }
-    }
 
     /** Creates an embedded task fragment. */
     TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
@@ -825,7 +822,25 @@
      * Returns whether this TaskFragment is currently forced to be hidden for any reason.
      */
     protected boolean isForceHidden() {
-        return false;
+        return mForceHiddenFlags != 0;
+    }
+
+    /**
+     * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
+     * @return Whether the force hidden state changed
+     */
+    boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
+        int newFlags = mForceHiddenFlags;
+        if (set) {
+            newFlags |= flags;
+        } else {
+            newFlags &= ~flags;
+        }
+        if (mForceHiddenFlags == newFlags) {
+            return false;
+        }
+        mForceHiddenFlags = newFlags;
+        return true;
     }
 
     protected boolean isForceTranslucent() {
@@ -969,7 +984,7 @@
         // A TaskFragment isn't translucent if it has at least one visible activity that occludes
         // this TaskFragment.
         return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
-                starting) == null;
+                starting, true /* ignoringKeyguard */) == null;
     }
 
     /**
@@ -982,7 +997,20 @@
             return true;
         }
         // Including finishing Activity if the TaskFragment is becoming invisible in the transition.
-        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
+        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
+                true /* ignoringKeyguard */) == null;
+    }
+
+    /**
+     * Like {@link  #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the
+     * windows rather than their visibility ignoring keyguard.
+     */
+    boolean isTranslucentAndVisible() {
+        if (!isAttached() || isForceHidden() || isForceTranslucent()) {
+            return true;
+        }
+        return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null,
+                false /* ignoringKeyguard */) == null;
     }
 
     ActivityRecord getTopNonFinishingActivity() {
@@ -1021,6 +1049,10 @@
         return getActivity(ActivityRecord::canBeTopRunning);
     }
 
+    /**
+     * Reports non-finishing activity count including this TaskFragment's child embedded
+     * TaskFragments' children activities.
+     */
     int getNonFinishingActivityCount() {
         final int[] runningActivityCount = new int[1];
         forAllActivities(a -> {
@@ -1031,6 +1063,20 @@
         return runningActivityCount[0];
     }
 
+    /**
+     * Returns {@code true} if there's any non-finishing direct children activity, which is not
+     * embedded in TaskFragments
+     */
+    boolean hasNonFinishingDirectActivity() {
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord activity = getChildAt(i).asActivityRecord();
+            if (activity != null && !activity.finishing) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     boolean isTopActivityFocusable() {
         final ActivityRecord r = topRunningActivity();
         return r != null ? r.isFocusable()
@@ -2099,13 +2145,6 @@
         return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
     }
 
-    /**
-     * Ensures all visible activities at or below the input activity have the right configuration.
-     */
-    void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
-        mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
-    }
-
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig) {
         computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
@@ -2929,14 +2968,27 @@
 
     @Override
     Dimmer getDimmer() {
-        // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
-        if (asTask() == null) {
+        // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
+        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {
             return mDimmer;
         }
 
         return super.getDimmer();
     }
 
+    /** Bounds to be used for dimming, as well as touch related tests. */
+    void getDimBounds(@NonNull Rect out) {
+        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) {
+            out.set(getTask().getBounds());
+        } else {
+            out.set(getBounds());
+        }
+    }
+
+    void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) {
+        mEmbeddedDimArea = embeddedDimArea;
+    }
+
     @Override
     void prepareSurfaces() {
         if (asTask() != null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 04164c2..34ae370 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -51,7 +51,6 @@
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOperation;
-import android.window.TaskFragmentOrganizerToken;
 import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
@@ -158,6 +157,15 @@
          */
         private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
 
+        /**
+         * Map from {@link TaskFragmentTransaction#getTransactionToken()} to a
+         * {@link Transition.ReadyCondition} that is waiting for the {@link TaskFragmentTransaction}
+         * to complete.
+         * @see #onTransactionHandled
+         */
+        private final ArrayMap<IBinder, Transition.ReadyCondition> mInFlightTransactions =
+                new ArrayMap<>();
+
         TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
                 boolean isSystemOrganizer) {
             mOrganizer = organizer;
@@ -174,7 +182,7 @@
         @Override
         public void binderDied() {
             synchronized (mGlobalLock) {
-                removeOrganizer(mOrganizer);
+                removeOrganizer(mOrganizer, "client died");
             }
         }
 
@@ -196,7 +204,7 @@
             mOrganizedTaskFragments.remove(taskFragment);
         }
 
-        void dispose() {
+        void dispose(@NonNull String reason) {
             boolean wasVisible = false;
             for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) {
                 final TaskFragment taskFragment = mOrganizedTaskFragments.get(i);
@@ -237,6 +245,10 @@
                 // Cleanup any running transaction to unblock the current transition.
                 onTransactionFinished(mDeferredTransitions.keyAt(i));
             }
+            for (int i = mInFlightTransactions.size() - 1; i >= 0; i--) {
+                // Cleanup any in-flight transactions to unblock the transition.
+                mInFlightTransactions.valueAt(i).meetAlternate("disposed(" + reason + ")");
+            }
             mOrganizer.asBinder().unlinkToDeath(this, 0 /* flags */);
         }
 
@@ -399,11 +411,6 @@
                 Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
                 return;
             }
-            onTransactionStarted(transaction.getTransactionToken());
-        }
-
-        /** Called when the transaction is sent to the organizer. */
-        void onTransactionStarted(@NonNull IBinder transactionToken) {
             if (!mWindowOrganizerController.getTransitionController().isCollecting()) {
                 return;
             }
@@ -411,9 +418,13 @@
                     .getCollectingTransitionId();
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Defer transition id=%d for TaskFragmentTransaction=%s", transitionId,
-                    transactionToken);
-            mDeferredTransitions.put(transactionToken, transitionId);
+                    transaction.getTransactionToken());
+            mDeferredTransitions.put(transaction.getTransactionToken(), transitionId);
             mWindowOrganizerController.getTransitionController().deferTransitionReady();
+            final Transition.ReadyCondition transactionApplied = new Transition.ReadyCondition(
+                    "task-fragment transaction", transaction);
+            mWindowOrganizerController.getTransitionController().waitFor(transactionApplied);
+            mInFlightTransactions.put(transaction.getTransactionToken(), transactionApplied);
         }
 
         /** Called when the transaction is finished. */
@@ -497,7 +508,7 @@
                 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
                         "Unregister task fragment organizer=%s uid=%d pid=%d",
                         organizer.asBinder(), uid, pid);
-                removeOrganizer(organizer);
+                removeOrganizer(organizer, "unregistered");
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -565,6 +576,11 @@
                     : null;
             if (state != null) {
                 state.onTransactionFinished(transactionToken);
+                final Transition.ReadyCondition condition =
+                        state.mInFlightTransactions.remove(transactionToken);
+                if (condition != null) {
+                    condition.meet();
+                }
             }
         }
     }
@@ -745,9 +761,9 @@
         }
     }
 
-    boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+    boolean isSystemOrganizer(@NonNull IBinder organizerToken) {
         final TaskFragmentOrganizerState state =
-                mTaskFragmentOrganizerState.get(token.asBinder());
+                mTaskFragmentOrganizerState.get(organizerToken);
         return state != null && state.mIsSystemOrganizer;
     }
 
@@ -778,7 +794,8 @@
         return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
     }
 
-    private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+    private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer,
+            @NonNull String reason) {
         final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(
                 organizer.asBinder());
         if (state == null) {
@@ -789,7 +806,7 @@
         // event dispatch as result of surface placement.
         mPendingTaskFragmentEvents.remove(organizer.asBinder());
         // remove all of the children of the organized TaskFragment
-        state.dispose();
+        state.dispose(reason);
         mTaskFragmentOrganizerState.remove(organizer.asBinder());
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 2b12e74..d8e18e4 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -284,9 +284,9 @@
         }
     }
 
-    void notifyTaskRemovedFromRecents(int taskId, int userId) {
+    void removeAndDeleteSnapshot(int taskId, int userId) {
         mCache.onIdRemoved(taskId);
-        mPersister.onTaskRemovedFromRecents(taskId, userId);
+        mPersister.removeSnapshot(taskId, userId);
     }
 
     void removeSnapshotCache(int taskId) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3e8c017..233daad 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -67,10 +67,11 @@
      * @param taskId The id of task that has been removed.
      * @param userId The id of the user the task belonged to.
      */
-    void onTaskRemovedFromRecents(int taskId, int userId) {
+    @Override
+    void removeSnapshot(int taskId, int userId) {
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
-            super.removeSnap(taskId, userId);
+            super.removeSnapshot(taskId, userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 882104a..f6b4972 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -236,7 +236,8 @@
     private IRemoteCallback mClientAnimationFinishCallback = null;
 
     private @TransitionState int mState = STATE_PENDING;
-    private final ReadyTracker mReadyTracker = new ReadyTracker();
+    private final ReadyTrackerOld mReadyTrackerOld = new ReadyTrackerOld();
+    final ReadyTracker mReadyTracker = new ReadyTracker(this);
 
     private int mRecentsDisplayId = INVALID_DISPLAY;
 
@@ -418,8 +419,8 @@
             if (transientRoot == null) continue;
             final WindowContainer<?> rootParent = transientRoot.getParent();
             if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
-            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
-                    .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
+                    .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
             if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
                 occludedCount++;
             }
@@ -656,7 +657,7 @@
             updateTransientFlags(info);
             mChanges.put(curr, info);
             if (isReadyGroup(curr)) {
-                mReadyTracker.addGroup(curr);
+                mReadyTrackerOld.addGroup(curr);
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
                                 + " Transition %d with root=%s", mSyncId, curr);
             }
@@ -703,6 +704,7 @@
         if (dc == null || mTargetDisplays.contains(dc)) return;
         mTargetDisplays.add(dc);
         addOnTopTasks(dc, mOnTopTasksStart);
+        mController.startPerfHintForDisplay(dc.mDisplayId);
     }
 
     /**
@@ -886,13 +888,18 @@
      */
     void setReady(WindowContainer wc, boolean ready) {
         if (!isCollecting() || mSyncId < 0) return;
-        mReadyTracker.setReadyFrom(wc, ready);
+        mReadyTrackerOld.setReadyFrom(wc, ready);
         applyReady();
     }
 
     private void applyReady() {
         if (mState < STATE_STARTED) return;
-        final boolean ready = mReadyTracker.allReady();
+        final boolean ready;
+        if (mController.useFullReadyTracking()) {
+            ready = mReadyTracker.isReady();
+        } else {
+            ready = mReadyTrackerOld.allReady();
+        }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                 "Set transition ready=%b %d", ready, mSyncId);
         boolean changed = mSyncEngine.setReady(mSyncId, ready);
@@ -908,22 +915,22 @@
 
     /**
      * Sets all possible ready groups to ready.
-     * @see ReadyTracker#setAllReady.
+     * @see ReadyTrackerOld#setAllReady
      */
     void setAllReady() {
         if (!isCollecting() || mSyncId < 0) return;
-        mReadyTracker.setAllReady();
+        mReadyTrackerOld.setAllReady();
         applyReady();
     }
 
     @VisibleForTesting
     boolean allReady() {
-        return mReadyTracker.allReady();
+        return mReadyTrackerOld.allReady();
     }
 
     /** This transition has all of its expected participants. */
     boolean isPopulated() {
-        return mState >= STATE_STARTED && mReadyTracker.allReady();
+        return mState >= STATE_STARTED && mReadyTrackerOld.allReady();
     }
 
     /**
@@ -1391,7 +1398,6 @@
             dc.handleCompleteDeferredRemoval();
         }
         validateKeyguardOcclusion();
-        validateVisibility();
 
         mState = STATE_FINISHED;
         // Rotation change may be deferred while there is a display change transition, so check
@@ -1438,6 +1444,13 @@
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
                 mSyncId);
         mForcePlaying = true;
+        // backwards since conditions are removed.
+        for (int i = mReadyTracker.mConditions.size() - 1; i >= 0; --i) {
+            mReadyTracker.mConditions.get(i).meetAlternate("play-now");
+        }
+        final ReadyCondition forcePlay = new ReadyCondition("force-play-now");
+        mReadyTracker.add(forcePlay);
+        forcePlay.meet();
         setAllReady();
         if (mState == STATE_COLLECTING) {
             start();
@@ -1483,6 +1496,21 @@
             return;
         }
 
+        if (mController.useFullReadyTracking()) {
+            if (mReadyTracker.mMet.isEmpty()) {
+                Slog.e(TAG, "#" + mSyncId + ": No conditions provided");
+            } else {
+                for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) {
+                    Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: "
+                            + mReadyTracker.mConditions.get(i));
+                }
+            }
+            for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
+                        mSyncId, mReadyTracker.mMet.get(i));
+            }
+        }
+
         // Commit the visibility of visible activities before calculateTransitionInfo(), so the
         // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
@@ -2766,29 +2794,6 @@
         }
     }
 
-    private void validateVisibility() {
-        for (int i = mTargets.size() - 1; i >= 0; --i) {
-            if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
-                return;
-            }
-        }
-        // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly.
-        // If the window container should be visible, then recover it.
-        mController.mStateValidators.add(() -> {
-            for (int i = mTargets.size() - 1; i >= 0; --i) {
-                final ChangeInfo change = mTargets.get(i);
-                if (!change.mContainer.isVisibleRequested()
-                        || change.mContainer.mSurfaceControl == null) {
-                    continue;
-                }
-                Slog.e(TAG, "Force show for visible " + change.mContainer
-                        + " which may be hidden by transition unexpectedly");
-                change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl);
-                change.mContainer.scheduleAnimation();
-            }
-        });
-    }
-
     /**
      * 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
@@ -2820,7 +2825,7 @@
             // if an activity is pausing, it will call setReady(ar, false) and wait for the next
             // resumed activity. Then do not set to ready because the transition only contains
             // partial participants. Otherwise the transition may only handle HIDE and miss OPEN.
-            if (!mReadyTracker.mUsed) {
+            if (!mReadyTrackerOld.mUsed) {
                 setReady(dc, true);
             }
         }
@@ -3082,19 +3087,160 @@
      * {@link #continueTransitionReady}
      */
     void deferTransitionReady() {
-        ++mReadyTracker.mDeferReadyDepth;
+        ++mReadyTrackerOld.mDeferReadyDepth;
         // Make sure it wait until #continueTransitionReady() is called.
         mSyncEngine.setReady(mSyncId, false);
     }
 
     /** This undoes one call to {@link #deferTransitionReady}. */
     void continueTransitionReady() {
-        --mReadyTracker.mDeferReadyDepth;
+        --mReadyTrackerOld.mDeferReadyDepth;
         // Apply ready in case it is waiting for the previous defer call.
         applyReady();
     }
 
     /**
+     * Represents a condition that must be met before an associated transition can be considered
+     * ready.
+     *
+     * Expected usage is that a ReadyCondition is created and then attached to a transition's
+     * ReadyTracker via {@link ReadyTracker#add}. After that, it is expected to monitor the state
+     * of the system and when the condition it represents is met, it will call
+     * {@link ReadyTracker#meet}.
+     *
+     * This base class is a simple explicit, named condition. A caller will create/attach the
+     * condition and then explicitly call {@link #meet} on it (which internally calls
+     * {@link ReadyTracker#meet}.
+     *
+     * Example:
+     * <pre>
+     *     ReadyCondition myCondition = new ReadyCondition("my condition");
+     *     transitionController.waitFor(myCondition);
+     *     ... Some operations ...
+     *     myCondition.meet();
+     * </pre>
+     */
+    static class ReadyCondition {
+        final String mName;
+
+        /** Just used for debugging */
+        final Object mDebugTarget;
+        ReadyTracker mTracker;
+        boolean mMet = false;
+
+        /** If set (non-null), then this is met by another reason besides state (eg. timeout). */
+        String mAlternate = null;
+
+        ReadyCondition(@NonNull String name) {
+            mName = name;
+            mDebugTarget = null;
+        }
+
+        ReadyCondition(@NonNull String name, @Nullable Object debugTarget) {
+            mName = name;
+            mDebugTarget = debugTarget;
+        }
+
+        protected String getDebugRep() {
+            if (mDebugTarget != null) {
+                return mName + ":" + mDebugTarget;
+            }
+            return mName;
+        }
+
+        @Override
+        public String toString() {
+            return "{" + getDebugRep() + (mAlternate != null ? " (" + mAlternate + ")" : "") + "}";
+        }
+
+        /**
+         * Instructs this condition to start tracking system state to detect when this is met.
+         * Don't call this directly; it is called when this object is attached to a transition's
+         * ready-tracker.
+         */
+        void startTracking() {
+        }
+
+        /**
+         * Immediately consider this condition met by an alternative reason (one which doesn't
+         * match the normal intent of this condition -- eg. a timeout).
+         */
+        void meetAlternate(@NonNull String reason) {
+            if (mMet) return;
+            mAlternate = reason;
+            meet();
+        }
+
+        /** Immediately consider this condition met. */
+        void meet() {
+            if (mMet) return;
+            if (mTracker == null) {
+                throw new IllegalStateException("Can't meet a condition before it is waited on");
+            }
+            mTracker.meet(this);
+        }
+    }
+
+    static class ReadyTracker {
+        /**
+         * Used as a place-holder in situations where the transition system isn't active (such as
+         * early-boot, mid shell crash/recovery, or when using legacy).
+         */
+        static final ReadyTracker NULL_TRACKER = new ReadyTracker(null);
+
+        private final Transition mTransition;
+
+        /** List of conditions that are still being waited on. */
+        final ArrayList<ReadyCondition> mConditions = new ArrayList<>();
+
+        /** List of already-met conditions. Fully-qualified for debugging. */
+        final ArrayList<ReadyCondition> mMet = new ArrayList<>();
+
+        ReadyTracker(Transition transition) {
+            mTransition = transition;
+        }
+
+        void add(@NonNull ReadyCondition condition) {
+            if (mTransition == null || !mTransition.mController.useFullReadyTracking()) {
+                condition.mTracker = NULL_TRACKER;
+                return;
+            }
+            mConditions.add(condition);
+            condition.mTracker = this;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d",
+                    condition, mTransition.mSyncId);
+            condition.startTracking();
+        }
+
+        void meet(@NonNull ReadyCondition condition) {
+            if (mTransition == null || !mTransition.mController.useFullReadyTracking()) return;
+            if (mTransition.mState >= STATE_PLAYING) {
+                Slog.w(TAG, "#%d: Condition met too late, already in state=" + mTransition.mState
+                        + ": " + condition);
+                return;
+            }
+            if (!mConditions.remove(condition)) {
+                if (mMet.contains(condition)) {
+                    throw new IllegalStateException("Can't meet the same condition more than once: "
+                            + condition + " #" + mTransition.mSyncId);
+                } else {
+                    throw new IllegalArgumentException("Can't meet a condition that isn't being "
+                            + "waited on: " + condition + " in #" + mTransition.mSyncId);
+                }
+            }
+            condition.mMet = true;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d"
+                    + " left)", condition, mTransition.mSyncId, mConditions.size());
+            mMet.add(condition);
+            mTransition.applyReady();
+        }
+
+        boolean isReady() {
+            return mConditions.isEmpty() && !mMet.isEmpty();
+        }
+    }
+
+    /**
      * The transition sync mechanism has 2 parts:
      *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
      *      launch or stop or get a new configuration?).
@@ -3107,7 +3253,7 @@
      * of readiness across the multiple groups. Currently, we assume that each display is a group
      * since that is how it has been until now.
      */
-    private static class ReadyTracker {
+    private static class ReadyTrackerOld {
         private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
 
         /**
@@ -3344,8 +3490,8 @@
             mFrozen.add(wc);
             final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
             changeInfo.mSnapshot = snapshotSurface;
-            if (isDisplayRotation) {
-                // This isn't cheap, so only do it for display rotations.
+            if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) {
+                // This isn't cheap, so only do it for rotation change.
                 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
                         buffer, screenshotBuffer.getColorSpace());
             }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 78afaa8..28c24c8 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -22,8 +22,10 @@
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.SystemPerformanceHinter.HINT_SF;
 
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -39,6 +41,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -48,6 +51,8 @@
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
+import android.window.SystemPerformanceHinter;
+import android.window.SystemPerformanceHinter.HighPerfSession;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
@@ -57,6 +62,7 @@
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.FgThread;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -125,6 +131,9 @@
     SnapshotController mSnapshotController;
     TransitionTracer mTransitionTracer;
 
+    private SystemPerformanceHinter mSystemPerformanceHinter;
+    private boolean mFullReadyTracking = false;
+
     private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
             new ArrayList<>();
 
@@ -176,6 +185,24 @@
 
     private final IBinder.DeathRecipient mTransitionPlayerDeath;
 
+    /**
+     * Tracks active perf sessions that boost frame rate and hint sf to increase its
+     * estimated work duration.
+     */
+    private final ArraySet<HighPerfSession> mHighPerfSessions = new ArraySet<>();
+
+
+    /**
+     * Starts a perf hint session which will boost the refresh rate for the display and change
+     * sf duration to handle larger workloads.
+     */
+    void startPerfHintForDisplay(int displayId) {
+        if (explicitRefreshRateHints()) {
+            mHighPerfSessions.add(mSystemPerformanceHinter.startSession(HINT_SF, displayId,
+                    "Transition collected"));
+        }
+    }
+
     static class QueuedTransition {
         final Transition mTransition;
         final OnStartCollect mOnStartCollect;
@@ -255,6 +282,13 @@
         mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled;
         registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
         setSyncEngine(wms.mSyncEngine);
+        setSystemPerformanceHinter(wms.mSystemPerformanceHinter);
+        mFullReadyTracking = Flags.transitReadyTracking();
+    }
+
+    @VisibleForTesting
+    void setSystemPerformanceHinter(SystemPerformanceHinter hinter) {
+        mSystemPerformanceHinter = hinter;
     }
 
     @VisibleForTesting
@@ -366,6 +400,14 @@
         return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION;
     }
 
+    boolean useFullReadyTracking() {
+        return mFullReadyTracking;
+    }
+
+    void setFullReadyTrackingForTest(boolean enabled) {
+        mFullReadyTracking = enabled;
+    }
+
     /**
      * @return {@code true} if transition is actively collecting changes. This is {@code false}
      * once a transition is playing
@@ -726,7 +768,7 @@
 
             final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
                     startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
-                    transition.getFlags());
+                    transition.getFlags(), transition.getSyncId());
 
             transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
             transition.mLogger.mRequest = request;
@@ -960,6 +1002,36 @@
         mValidateDisplayVis.clear();
     }
 
+    void onVisibleWithoutCollectingTransition(WindowContainer<?> wc, String caller) {
+        final boolean isPlaying = !mPlayingTransitions.isEmpty();
+        Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying
+                + " caller=" + caller);
+        if (!isPlaying) {
+            enforceSurfaceVisible(wc);
+            return;
+        }
+        // Update surface visibility after the playing transitions are finished, so the last
+        // visibility won't be replaced by the finish transaction of transition.
+        mStateValidators.add(() -> {
+            if (wc.isVisibleRequested()) {
+                enforceSurfaceVisible(wc);
+            }
+        });
+    }
+
+    private void enforceSurfaceVisible(WindowContainer<?> wc) {
+        if (wc.mSurfaceControl == null) return;
+        wc.getSyncTransaction().show(wc.mSurfaceControl);
+        // Force showing the parents because they may be hidden by previous transition.
+        for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
+                p = p.getParent()) {
+            if (p.mSurfaceControl != null) {
+                p.getSyncTransaction().show(p.mSurfaceControl);
+            }
+        }
+        wc.scheduleAnimation();
+    }
+
     /**
      * Called when the transition has a complete set of participants for its operation. In other
      * words, it is when the transition is "ready" but is still waiting for participants to draw.
@@ -1006,12 +1078,20 @@
             // legacy sync
             mSyncEngine.startSyncSet(queued.mLegacySync);
         }
-        // Post this so that the now-playing transition logic isn't interrupted.
-        mAtm.mH.post(() -> {
-            synchronized (mAtm.mGlobalLock) {
-                queued.mOnStartCollect.onCollectStarted(true /* deferred */);
-            }
-        });
+        if (queued.mTransition != null
+                && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) {
+            // SLEEP transitions are special in that they don't collect anything (in fact if they
+            // do collect things it can cause problems). So, we need to run it's onCollectStarted
+            // immediately.
+            queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+        } else {
+            // Post this so that the now-playing transition logic isn't interrupted.
+            mAtm.mH.post(() -> {
+                synchronized (mAtm.mGlobalLock) {
+                    queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+                }
+            });
+        }
     }
 
     void moveToPlaying(Transition transition) {
@@ -1164,18 +1244,27 @@
         final boolean animatingState = !mPlayingTransitions.isEmpty()
                     || (mCollectingTransition != null && mCollectingTransition.isStarted());
         if (animatingState && !mAnimatingState) {
-            t.setEarlyWakeupStart();
+            if (!explicitRefreshRateHints()) {
+                t.setEarlyWakeupStart();
+            }
             // Usually transitions put quite a load onto the system already (with all the things
             // happening in app), so pause task snapshot persisting to not increase the load.
             mSnapshotController.setPause(true);
             mAnimatingState = true;
             Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
-            t.setEarlyWakeupEnd();
+            if (!explicitRefreshRateHints()) {
+                t.setEarlyWakeupEnd();
+            }
             mAtm.mWindowManager.scheduleAnimationLocked();
             mSnapshotController.setPause(false);
             mAnimatingState = false;
             Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
+            // We close all perf sessions here when all transitions finish. The sessions are created
+            // when we collect transitions because we have access to the display id.
+            for (HighPerfSession perfSession : mHighPerfSessions) {
+                perfSession.close();
+            }
         }
     }
 
@@ -1397,6 +1486,19 @@
         applySync.accept(false /* deferred */);
     }
 
+    /**
+     * Instructs the collecting transition to wait until `condition` is met before it is
+     * considered ready.
+     */
+    void waitFor(@NonNull Transition.ReadyCondition condition) {
+        if (mCollectingTransition == null) {
+            Slog.e(TAG, "No collecting transition available to wait for " + condition);
+            condition.mTracker = Transition.ReadyTracker.NULL_TRACKER;
+            return;
+        }
+        mCollectingTransition.mReadyTracker.add(condition);
+    }
+
     interface OnStartCollect {
         void onCollectStarted(boolean deferred);
     }
@@ -1514,8 +1616,8 @@
         TransitionInfo mInfo;
 
         private String buildOnSendLog() {
-            StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
-                    .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+            StringBuilder sb = new StringBuilder("Sent Transition (#").append(mSyncId)
+                    .append(") createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
             if (mRequest != null) {
                 sb.append(" via request=").append(mRequest);
             }
@@ -1539,7 +1641,8 @@
         void logOnSend() {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    startWCT=%s", mStartWCT);
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s", mInfo);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s",
+                    mInfo.toString("    " /* prefix */));
         }
 
         private static String toMsString(long nanos) {
@@ -1547,8 +1650,8 @@
         }
 
         private String buildOnFinishLog() {
-            StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId)
-                    .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+            StringBuilder sb = new StringBuilder("Finish Transition (#").append(mSyncId)
+                    .append("): created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
             sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));
             if (mRequestTimeNs != 0) {
                 sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs));
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 41c1e79..c071396 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -70,6 +70,9 @@
     }
 
     boolean isVisibilityUnknown(ActivityRecord r) {
+        if (mUnknownApps.isEmpty()) {
+            return false;
+        }
         return mUnknownApps.containsKey(r);
     }
 
@@ -90,6 +93,9 @@
     }
 
     void appRemovedOrHidden(@NonNull ActivityRecord activity) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
             Slog.d(TAG, "App removed or hidden activity=" + activity);
         }
@@ -117,8 +123,11 @@
      * Notifies that {@param activity} has finished resuming.
      */
     void notifyAppResumedFinished(@NonNull ActivityRecord activity) {
-        if (mUnknownApps.containsKey(activity)
-                && mUnknownApps.get(activity) == UNKNOWN_STATE_WAITING_RESUME) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
+        final Integer state = mUnknownApps.get(activity);
+        if (state != null && state == UNKNOWN_STATE_WAITING_RESUME) {
             if (DEBUG_UNKNOWN_APP_VISIBILITY) {
                 Slog.d(TAG, "App resume finished activity=" + activity);
             }
@@ -130,13 +139,16 @@
      * Notifies that {@param activity} has relaid out.
      */
     void notifyRelayouted(@NonNull ActivityRecord activity) {
-        if (!mUnknownApps.containsKey(activity)) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
+        final Integer state = mUnknownApps.get(activity);
+        if (state == null) {
             return;
         }
         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
             Slog.d(TAG, "App relayouted appWindow=" + activity);
         }
-        int state = mUnknownApps.get(activity);
         if (state == UNKNOWN_STATE_WAITING_RELAYOUT || activity.mStartingWindow != null) {
             mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
             mDisplayContent.notifyKeyguardFlagsChanged();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7e5dabb..33ef3c5 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -45,7 +45,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.util.MathUtils;
 import android.util.Slog;
@@ -85,13 +84,8 @@
     // to another, and this is the previous wallpaper target.
     private WindowState mPrevWallpaperTarget = null;
 
-    private float mLastWallpaperX = -1;
-    private float mLastWallpaperY = -1;
-    private float mLastWallpaperXStep = -1;
-    private float mLastWallpaperYStep = -1;
     private float mLastWallpaperZoomOut = 0;
-    private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
-    private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+
     // Whether COMMAND_FREEZE was dispatched.
     private boolean mLastFrozen = false;
 
@@ -116,16 +110,12 @@
     private static final int WALLPAPER_DRAW_TIMEOUT = 2;
     private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
 
-    private boolean mShouldUpdateZoom;
-
     @Nullable private Point mLargestDisplaySize = null;
 
     private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
 
     private boolean mShouldOffsetWallpaperCenter;
 
-    final boolean mIsLockscreenLiveWallpaperEnabled;
-
     private final Consumer<WindowState> mFindWallpapers = w -> {
         if (w.mAttrs.type == TYPE_WALLPAPER) {
             WallpaperWindowToken token = w.mToken.asWallpaperToken();
@@ -243,9 +233,6 @@
     WallpaperController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
-        mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
-
         Resources resources = service.mContext.getResources();
         mMinWallpaperScale =
                 resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
@@ -370,6 +357,7 @@
         // Full size of the wallpaper (usually larger than bounds above to parallax scroll when
         // swiping through Launcher pages).
         final Rect wallpaperFrame = wallpaperWin.getFrame();
+        WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
 
         final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
         final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
@@ -394,10 +382,10 @@
         // The 0 to 1 scale is because the "length" varies depending on how many home screens you
         // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for
         // LTR, and the opposite for RTL).
-        float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX;
+        float wpx = token.mWallpaperX >= 0 ? token.mWallpaperX : defaultWallpaperX;
         // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen
         // when scrolling.
-        float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
+        float wpxs = token.mWallpaperXStep >= 0 ? token.mWallpaperXStep : -1.0f;
         // Difference between width of wallpaper image, and the last size of the wallpaper.
         // This is the horizontal surplus from the prior configuration.
         int availw = diffWidth;
@@ -406,10 +394,10 @@
                 wallpaperWin.isRtl());
         availw -= displayOffset;
         int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
-        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+        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 += mLastWallpaperDisplayOffsetX;
+            offset += token.mWallpaperDisplayOffsetX;
         } else if (!wallpaperWin.isRtl()) {
             // In RTL the offset is calculated so that the wallpaper ends up right aligned (see
             // offset above).
@@ -423,11 +411,11 @@
             rawChanged = true;
         }
 
-        float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
-        float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
+        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;
-        if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-            offset += mLastWallpaperDisplayOffsetY;
+        if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            offset += token.mWallpaperDisplayOffsetY;
         }
         newYOffset = offset;
 
@@ -542,15 +530,17 @@
             window.mWallpaperY = y;
             window.mWallpaperXStep = xStep;
             window.mWallpaperYStep = yStep;
-            updateWallpaperOffsetLocked(window, true);
+            updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync);
         }
     }
 
     void setWallpaperZoomOut(WindowState window, float zoom) {
         if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) {
             window.mWallpaperZoomOut = zoom;
-            mShouldUpdateZoom = true;
-            updateWallpaperOffsetLocked(window, false);
+            computeLastWallpaperZoomOut();
+            for (WallpaperWindowToken token : mWallpaperTokens) {
+                token.updateWallpaperOffset(false);
+            }
         }
     }
 
@@ -565,7 +555,7 @@
         if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y)  {
             window.mWallpaperDisplayOffsetX = x;
             window.mWallpaperDisplayOffsetY = y;
-            updateWallpaperOffsetLocked(window, true);
+            updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync);
         }
     }
 
@@ -598,43 +588,48 @@
             // zoom effect from home.
             target = changingTarget;
         }
-        if (target != null) {
-            if (target.mWallpaperX >= 0) {
-                mLastWallpaperX = target.mWallpaperX;
-            } else if (changingTarget.mWallpaperX >= 0) {
-                mLastWallpaperX = changingTarget.mWallpaperX;
-            }
-            if (target.mWallpaperY >= 0) {
-                mLastWallpaperY = target.mWallpaperY;
-            } else if (changingTarget.mWallpaperY >= 0) {
-                mLastWallpaperY = changingTarget.mWallpaperY;
-            }
-            computeLastWallpaperZoomOut();
-            if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
-            } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
-            }
-            if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
-            } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
-            }
-            if (target.mWallpaperXStep >= 0) {
-                mLastWallpaperXStep = target.mWallpaperXStep;
-            } else if (changingTarget.mWallpaperXStep >= 0) {
-                mLastWallpaperXStep = changingTarget.mWallpaperXStep;
-            }
-            if (target.mWallpaperYStep >= 0) {
-                mLastWallpaperYStep = target.mWallpaperYStep;
-            } else if (changingTarget.mWallpaperYStep >= 0) {
-                mLastWallpaperYStep = changingTarget.mWallpaperYStep;
-            }
-        }
 
-        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
-            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync);
+        WallpaperWindowToken token = getTokenForTarget(target);
+        if (token == null) return;
+
+        if (target.mWallpaperX >= 0) {
+            token.mWallpaperX = target.mWallpaperX;
+        } else if (changingTarget.mWallpaperX >= 0) {
+            token.mWallpaperX = changingTarget.mWallpaperX;
         }
+        if (target.mWallpaperY >= 0) {
+            token.mWallpaperY = target.mWallpaperY;
+        } else if (changingTarget.mWallpaperY >= 0) {
+            token.mWallpaperY = changingTarget.mWallpaperY;
+        }
+        if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
+        } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
+        }
+        if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
+        } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
+        }
+        if (target.mWallpaperXStep >= 0) {
+            token.mWallpaperXStep = target.mWallpaperXStep;
+        } else if (changingTarget.mWallpaperXStep >= 0) {
+            token.mWallpaperXStep = changingTarget.mWallpaperXStep;
+        }
+        if (target.mWallpaperYStep >= 0) {
+            token.mWallpaperYStep = target.mWallpaperYStep;
+        } else if (changingTarget.mWallpaperYStep >= 0) {
+            token.mWallpaperYStep = changingTarget.mWallpaperYStep;
+        }
+        token.updateWallpaperOffset(sync);
+    }
+
+    private WallpaperWindowToken getTokenForTarget(WindowState target) {
+        if (target == null) return null;
+        WindowState window = mFindResults.getTopWallpaper(
+                target.canShowWhenLocked() && mService.isKeyguardLocked());
+        return window == null ? null : window.mToken.asWallpaperToken();
     }
 
     void clearLastWallpaperTimeoutTime() {
@@ -805,10 +800,11 @@
         // all wallpapers go behind it.
         findWallpaperTarget();
         updateWallpaperWindowsTarget(mFindResults);
+        WallpaperWindowToken token = getTokenForTarget(mWallpaperTarget);
 
         // The window is visible to the compositor...but is it visible to the user?
         // That is what the wallpaper cares about.
-        final boolean visible = mWallpaperTarget != null;
+        final boolean visible = token != null;
         if (DEBUG_WALLPAPER) {
             Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
                     + mDisplayContent.getDisplayId());
@@ -816,19 +812,18 @@
 
         if (visible) {
             if (mWallpaperTarget.mWallpaperX >= 0) {
-                mLastWallpaperX = mWallpaperTarget.mWallpaperX;
-                mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
+                token.mWallpaperX = mWallpaperTarget.mWallpaperX;
+                token.mWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
             }
-            computeLastWallpaperZoomOut();
             if (mWallpaperTarget.mWallpaperY >= 0) {
-                mLastWallpaperY = mWallpaperTarget.mWallpaperY;
-                mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
+                token.mWallpaperY = mWallpaperTarget.mWallpaperY;
+                token.mWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
             }
             if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
+                token.mWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
             }
             if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
+                token.mWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
             }
         }
 
@@ -1020,13 +1015,11 @@
      * we'll have conflicts and break the "depth system" mental model.
      */
     private void computeLastWallpaperZoomOut() {
-        if (mShouldUpdateZoom) {
-            mLastWallpaperZoomOut = 0;
-            mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true);
-            mShouldUpdateZoom = false;
-        }
+        mLastWallpaperZoomOut = 0;
+        mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true);
     }
 
+
     private float zoomOutToScale(float zoomOut) {
         return MathUtils.lerp(mMinWallpaperScale, mMaxWallpaperScale, 1 - zoomOut);
     }
@@ -1034,19 +1027,28 @@
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("displayId="); pw.println(mDisplayContent.getDisplayId());
         pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget);
+        pw.print(prefix); pw.print("mLastWallpaperZoomOut="); pw.println(mLastWallpaperZoomOut);
         if (mPrevWallpaperTarget != null) {
             pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget);
         }
-        pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX);
-        pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
-        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE
-                || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-            pw.print(prefix);
-            pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
-            pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
+
+        for (WallpaperWindowToken t : mWallpaperTokens) {
+            pw.print(prefix); pw.println("token " + t + ":");
+            pw.print(prefix); pw.print("  canShowWhenLocked="); pw.println(t.canShowWhenLocked());
+            dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX);
+            dumpValue(pw, prefix, "mWallpaperY", t.mWallpaperY);
+            dumpValue(pw, prefix, "mWallpaperXStep", t.mWallpaperXStep);
+            dumpValue(pw, prefix, "mWallpaperYStep", t.mWallpaperYStep);
+            dumpValue(pw, prefix, "mWallpaperDisplayOffsetX", t.mWallpaperDisplayOffsetX);
+            dumpValue(pw, prefix, "mWallpaperDisplayOffsetY", t.mWallpaperDisplayOffsetY);
         }
     }
 
+    private void dumpValue(PrintWriter pw, String prefix, String valueName, float value) {
+        pw.print(prefix); pw.print("  " + valueName + "=");
+        pw.println(value >= 0 ? value : "NA");
+    }
+
     /** Helper class for storing the results of a wallpaper target find operation. */
     final private static class FindWallpaperTargetResult {
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index c7fd147..15bd607 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -42,6 +42,12 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
 
     private boolean mShowWhenLocked = false;
+    float mWallpaperX = -1;
+    float mWallpaperY = -1;
+    float mWallpaperXStep = -1;
+    float mWallpaperYStep = -1;
+    int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
+    int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
 
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
@@ -76,18 +82,16 @@
             return;
         }
         mShowWhenLocked = showWhenLocked;
-        if (mDisplayContent.mWallpaperController.mIsLockscreenLiveWallpaperEnabled) {
-            // Move the window token to the front (private) or back (showWhenLocked). This is
-            // possible
-            // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
-            // windows.
-            final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
+        // Move the window token to the front (private) or back (showWhenLocked). This is
+        // possible
+        // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
+        // windows.
+        final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
 
-            // Note: Moving all the way to the front or back breaks ordering based on addition
-            // times.
-            // We should never have more than one non-animating token of each type.
-            getParent().positionChildAt(position, this /* child */, false /*includingParents */);
-        }
+        // Note: Moving all the way to the front or back breaks ordering based on addition
+        // times.
+        // We should never have more than one non-animating token of each type.
+        getParent().positionChildAt(position, this /* child */, false /*includingParents */);
     }
 
     boolean canShowWhenLocked() {
@@ -111,7 +115,8 @@
         final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
-            if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
+            if (wallpaperController.updateWallpaperOffset(wallpaper,
+                    sync && !mWmService.mFlags.mWallpaperOffsetAsync)) {
                 // We only want to be synchronous with one wallpaper.
                 sync = false;
             }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4a0f44b..8f884d2f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2706,6 +2706,10 @@
         return mLastLayer;
     }
 
+    SurfaceControl getLastRelativeLayer() {
+        return mLastRelativeToLayer;
+    }
+
     protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
         if (mSurfaceFreezer.hasLeash()) {
             // When the freezer has created animation leash parent for the window, set the layer
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index fbd226e..1456184 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -51,7 +51,8 @@
     final Rect mFrame = new Rect();
 
     /**
-     * The last real frame that was reported to the client.
+     * The frame used to check if mFrame is changed, e.g., moved or resized.  It will be committed
+     * after handling the moving or resizing windows.
      */
     final Rect mLastFrame = new Rect();
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
new file mode 100644
index 0000000..4667710
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Utility class to read the flags used in the WindowManager server.
+ *
+ * It is not very cheap to read trunk stable flag, so having a centralized place to cache the flag
+ * values in the system server side.
+ *
+ * Flags should be defined in `core.java.android.window.flags` to allow access from client side.
+ *
+ * To override flag:
+ *   adb shell device_config put [namespace] [package].[name] [true/false]
+ *   adb reboot
+ *
+ * To access in wm:
+ *   {@link WindowManagerService#mFlags}
+ *
+ * Notes:
+ *   The system may use flags at anytime, so changing flags will only take effect after device
+ *   reboot. Otherwise, it may result unexpected behavior, such as broken transition.
+ *   When a flag needs to be read from both the server side and the client side, changing the flag
+ *   value will result difference in server and client until device reboot.
+ */
+class WindowManagerFlags {
+
+    /* Start Available Flags */
+
+    final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
+
+    final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
+
+    final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
+
+    /* End Available Flags */
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c9107e8..4a074ff 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -304,6 +304,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
+import android.window.SystemPerformanceHinter;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 import android.window.WindowContextInfo;
@@ -547,6 +548,8 @@
     @VisibleForTesting
     WindowManagerPolicy mPolicy;
 
+    final WindowManagerFlags mFlags;
+
     final IActivityManager mActivityManager;
     final ActivityManagerInternal mAmInternal;
     final UserManagerInternal mUmInternal;
@@ -585,6 +588,12 @@
     final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
 
     /**
+     * Windows that their frames are being changed.  Used so we can clear the frame-changing states
+     * after handling the moved or resized windows.
+     */
+    final ArrayList<WindowState> mFrameChangingWindows = new ArrayList<>();
+
+    /**
      * Mapping of displayId to {@link DisplayImePolicy}.
      * Note that this can be accessed without holding the lock.
      */
@@ -1036,6 +1045,8 @@
         sThreadPriorityBooster.reset();
     }
 
+    SystemPerformanceHinter mSystemPerformanceHinter;
+
     void openSurfaceTransaction() {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
@@ -1153,6 +1164,7 @@
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
         mContext = context;
+        mFlags = new WindowManagerFlags();
         mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mAllowBootMessages = showBootMsgs;
         mLimitedAlphaCompositing = context.getResources().getBoolean(
@@ -1329,6 +1341,13 @@
         mBlurController = new BlurController(mContext, mPowerManager);
         mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
         mAccessibilityController = new AccessibilityController(this);
+        mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
+            synchronized (mGlobalLock) {
+                DisplayContent dc = mRoot.getDisplayContent(displayId);
+                return (dc == null) ? null : dc.getSurfaceControl();
+            }
+
+        }, mTransactionFactory);
     }
 
     DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
@@ -2248,6 +2267,7 @@
                     }
                 }
 
+                final boolean wasTrustedOverlay = win.isWindowTrustedOverlay();
                 flagChanges = win.mAttrs.flags ^ attrs.flags;
                 privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
                 attrChanges = win.mAttrs.copyFrom(attrs);
@@ -2260,6 +2280,9 @@
                 if (layoutChanged && win.providesDisplayDecorInsets()) {
                     configChanged = displayPolicy.updateDecorInsetsInfo();
                 }
+                if (wasTrustedOverlay != win.isWindowTrustedOverlay()) {
+                    win.updateTrustedOverlay();
+                }
                 if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
                         || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
                     win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -3101,10 +3124,15 @@
 
     @Override
     public void notifyKeyguardTrustedChanged() {
-        synchronized (mGlobalLock) {
-            if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
-                mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
+                    mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+                }
             }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
     }
 
@@ -5281,7 +5309,11 @@
     public void displayReady() {
         synchronized (mGlobalLock) {
             if (mMaxUiWidth > 0) {
-                mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));
+                mRoot.forAllDisplays(dc -> {
+                    if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) {
+                        dc.setMaxUiWidth(mMaxUiWidth);
+                    }
+                });
             }
             applyForcedPropertiesForDefaultDisplay();
             mAnimator.ready();
@@ -8435,16 +8467,18 @@
                     return true;
                 }
                 // For a task session, find the activity identified by the launch cookie.
-                final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+                final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(
                         incomingSession.getTokenToRecord());
-                if (wct == null) {
+                if (wci == null) {
                     Slog.w(TAG, "Handling a new recording session; unable to find the "
                             + "WindowContainerToken");
                     return false;
                 }
                 // Replace the launch cookie in the session details with the task's
                 // WindowContainerToken.
-                incomingSession.setTokenToRecord(wct.asBinder());
+                incomingSession.setTokenToRecord(wci.getToken().asBinder());
+                // Also replace the UNKNOWN target UID with the actual UID.
+                incomingSession.setTargetUid(wci.getUid());
                 mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
                         WindowManagerService.this);
                 return true;
@@ -8772,21 +8806,41 @@
         mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
     }
 
+    @VisibleForTesting
+    static class WindowContainerInfo {
+        private final int mUid;
+        @NonNull private final WindowContainerToken mToken;
+
+        private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) {
+            this.mUid = uid;
+            this.mToken = token;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        @NonNull
+        public WindowContainerToken getToken() {
+            return mToken;
+        }
+    }
+
     /**
-     * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
-     * with the given launch cookie.
+     * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with
+     * the given launch cookie.
      *
      * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
-     *                     activity
+     *     activity
      * @return a token representing the task containing the activity started with the given launch
-     * cookie, or {@code null} if the token couldn't be found.
+     *     cookie, or {@code null} if the token couldn't be found.
      */
     @VisibleForTesting
     @Nullable
-    WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+    WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {
         // Find the activity identified by the launch cookie.
-        final ActivityRecord targetActivity = mRoot.getActivity(
-                activity -> activity.mLaunchCookie == launchCookie);
+        final ActivityRecord targetActivity =
+                mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
         if (targetActivity == null) {
             Slog.w(TAG, "Unable to find the activity for this launch cookie");
             return null;
@@ -8801,7 +8855,7 @@
             Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
             return null;
         }
-        return taskWindowContainerToken;
+        return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dd9a88f..eb60aab 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -24,7 +24,9 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
@@ -34,6 +36,8 @@
 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;
+import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
+import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
 import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -61,6 +65,7 @@
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -300,9 +305,12 @@
                     // This is a direct call from shell, so the entire transition lifecycle is
                     // contained in the provided transaction if provided. Thus, we can setReady
                     // immediately after apply.
+                    final Transition.ReadyCondition wctApplied =
+                            new Transition.ReadyCondition("start WCT applied");
                     final boolean needsSetReady = t != null;
                     final Transition nextTransition = new Transition(type, 0 /* flags */,
                             mTransitionController, mService.mWindowManager.mSyncEngine);
+                    nextTransition.mReadyTracker.add(wctApplied);
                     nextTransition.calcParallelCollectType(wct);
                     mTransitionController.startCollectOrQueue(nextTransition,
                             (deferred) -> {
@@ -310,6 +318,7 @@
                                 nextTransition.mLogger.mStartWCT = wct;
                                 applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
                                         deferred);
+                                wctApplied.meet();
                                 if (needsSetReady) {
                                     // TODO(b/294925498): Remove this once we have accurate ready
                                     //                    tracking.
@@ -324,6 +333,15 @@
                             });
                     return nextTransition.getToken();
                 }
+                // Currently, application of wct can span multiple looper loops (ie.
+                // waitAsyncStart), so add a condition to ensure that it finishes applying.
+                final Transition.ReadyCondition wctApplied;
+                if (t != null) {
+                    wctApplied = new Transition.ReadyCondition("start WCT applied");
+                    transition.mReadyTracker.add(wctApplied);
+                } else {
+                    wctApplied = null;
+                }
                 // The transition already started collecting before sending a request to shell,
                 // so just start here.
                 if (!transition.isCollecting() && !transition.isForcePlaying()) {
@@ -331,6 +349,9 @@
                             + " means Shell took too long to respond to a request. WM State may be"
                             + " incorrect now, please file a bug");
                     applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+                    if (wctApplied != null) {
+                        wctApplied.meet();
+                    }
                     return transition.getToken();
                 }
                 transition.mLogger.mStartWCT = wct;
@@ -339,11 +360,17 @@
                         synchronized (mService.mGlobalLock) {
                             transition.start();
                             applyTransaction(wct, -1 /* syncId */, transition, caller);
+                            if (wctApplied != null) {
+                                wctApplied.meet();
+                            }
                         }
                     });
                 } else {
                     transition.start();
                     applyTransaction(wct, -1 /* syncId */, transition, caller);
+                    if (wctApplied != null) {
+                        wctApplied.meet();
+                    }
                 }
                 // Since the transition is already provided, it means WMCore is determining the
                 // "readiness lifecycle" outside the provided transaction, so don't set ready here.
@@ -821,6 +848,7 @@
             return TRANSACT_EFFECTS_NONE;
         }
 
+        int effects = TRANSACT_EFFECTS_NONE;
         // When the TaskFragment is resized, we may want to create a change transition for it, for
         // which we want to defer the surface update until we determine whether or not to start
         // change transition.
@@ -843,7 +871,14 @@
             c.getConfiguration().windowConfiguration.setBounds(absBounds);
             taskFragment.setRelativeEmbeddedBounds(relBounds);
         }
-        final int effects = applyChanges(taskFragment, c);
+        if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
+            if (taskFragment.setForceHidden(
+                    FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+        effects |= applyChanges(taskFragment, c);
+
         if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
             taskFragment.initializeChangeTransition(mTmpBounds0);
         }
@@ -1146,9 +1181,13 @@
             }
             case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
                 final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
-                if (container == null || container.asDisplayArea() == null
-                        || !container.isAttached()) {
-                    Slog.e(TAG, "Attempt to operate on unknown or detached display area: "
+                if (container == null || !container.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+                            + container);
+                    break;
+                }
+                if (container.asTask() == null && container.asDisplayArea() == null) {
+                    Slog.e(TAG, "Cannot set always-on-top on non-task or non-display area: "
                             + container);
                     break;
                 }
@@ -1393,6 +1432,24 @@
                 taskFragment.setIsolatedNav(isolatedNav);
                 break;
             }
+            case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
+                final Task task = taskFragment.getTask();
+                if (task != null) {
+                    task.mChildren.remove(taskFragment);
+                    task.mChildren.add(0, taskFragment);
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
+            case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
+                final Task task = taskFragment.getTask();
+                if (task != null) {
+                    task.mChildren.remove(taskFragment);
+                    task.mChildren.add(taskFragment);
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
         }
         return effects;
     }
@@ -1420,6 +1477,18 @@
             return false;
         }
 
+        if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK
+                || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK)
+                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            final Throwable exception = new SecurityException(
+                    "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or "
+                            + "OP_TYPE_REORDER_TO_TOP_OF_TASK."
+            );
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+                    opType, exception);
+            return false;
+        }
+
         final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
         return secondaryFragmentToken == null
                 || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
@@ -1920,6 +1989,11 @@
      * For config change on {@link TaskFragment}, we only support the following operations:
      * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)},
      * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}.
+     *
+     * For a system organizer, we additionally support
+     * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and
+     * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See
+     * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)}
      */
     private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func,
             @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change,
@@ -1938,31 +2012,49 @@
             throw new SecurityException(msg);
         }
 
-        final int changeMask = change.getChangeMask();
-        final int configSetMask = change.getConfigSetMask();
-        final int windowSetMask = change.getWindowSetMask();
-        if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0
-                && change.getWindowingMode() >= 0) {
-            // The change contains only setWindowingMode, which is allowed.
-            return;
+        final int originalChangeMask = change.getChangeMask();
+        final int originalConfigSetMask = change.getConfigSetMask();
+        final int originalWindowSetMask = change.getWindowSetMask();
+
+        int changeMaskToBeChecked = originalChangeMask;
+        int configSetMaskToBeChecked = originalConfigSetMask;
+        int windowSetMaskToBeChecked = originalWindowSetMask;
+
+        if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            // System organizer is allowed to update the hidden and focusable state.
+            // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here.
+            changeMaskToBeChecked &= ~CHANGE_HIDDEN;
+            changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;
         }
-        if (changeMask != CHANGE_RELATIVE_BOUNDS
-                || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION
-                || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) {
-            // None of the change should be requested from a TaskFragment organizer except
-            // setRelativeBounds and setWindowingMode.
-            // For setRelativeBounds, we don't need to check whether it is outside of the Task
+
+        // setRelativeBounds is allowed.
+        if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0
+                && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
+                && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
+            // For setRelativeBounds, we don't need to check whether it is outside the Task
             // bounds, because it is possible that the Task is also resizing, for which we don't
             // want to throw an exception. The bounds will be adjusted in
             // TaskFragment#translateRelativeBoundsToAbsoluteBounds.
-            String msg = "Permission Denial: " + func + " from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply changes of changeMask=" + changeMask
-                    + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask
-                    + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
+            changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS;
+            configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+            windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS;
         }
+
+        if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0
+                && windowSetMaskToBeChecked == 0) {
+            // All the changes have been checked.
+            // Note that setWindowingMode is always allowed, so we don't need to check the mask.
+            return;
+        }
+
+        final String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                + " trying to apply changes of changeMask=" + originalChangeMask
+                + " configSetMask=" + originalConfigSetMask
+                + " windowSetMask=" + originalWindowSetMask
+                + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
     }
 
     private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
@@ -2019,7 +2111,7 @@
         TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
         taskFragment.setTaskFragmentOrganizer(organizerToken,
                 ownerActivity.getUid(), ownerActivity.info.processName,
-                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
+                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder()));
         final int position;
         if (creationParams.getPairedPrimaryFragmentToken() != null) {
             // When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index e769a27..a74a707 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -42,7 +42,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 import static com.android.server.wm.WindowManagerService.MY_PID;
 
 import static java.util.Objects.requireNonNull;
@@ -631,22 +630,23 @@
      */
     @HotPath(caller = HotPath.START_SERVICE)
     public boolean areBackgroundFgsStartsAllowed() {
-        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(),
-                true /* isCheckingForFgsStart */) != BAL_BLOCK;
+        return areBackgroundActivityStartsAllowed(
+                mAtm.getBalAppSwitchesState(),
+                true /* isCheckingForFgsStart */).allows();
     }
 
-    @BackgroundActivityStartController.BalCode
-    int areBackgroundActivityStartsAllowed(int appSwitchState) {
-        return areBackgroundActivityStartsAllowed(appSwitchState,
+    BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed(
+            int appSwitchState) {
+        return areBackgroundActivityStartsAllowed(
+                appSwitchState,
                 false /* isCheckingForFgsStart */);
     }
 
-    @BackgroundActivityStartController.BalCode
-    private int areBackgroundActivityStartsAllowed(int appSwitchState,
-            boolean isCheckingForFgsStart) {
-        return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName,
-                appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(),
-                mInstrumentingWithBackgroundActivityStartPrivileges,
+    private BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed(
+            int appSwitchState, boolean isCheckingForFgsStart) {
+        return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid,
+                mInfo.packageName, appSwitchState, isCheckingForFgsStart,
+                hasActivityInVisibleTask(), mInstrumentingWithBackgroundActivityStartPrivileges,
                 mAtm.getLastStopAppSwitchesTime(),
                 mLastActivityLaunchTime, mLastActivityFinishTime);
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4beec2b..f14a6f9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -186,6 +186,7 @@
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyCache;
+import android.app.servertransaction.WindowStateResizeItem;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
@@ -1188,7 +1189,21 @@
         }
     }
 
-    public boolean isWindowTrustedOverlay() {
+    @Override
+    void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
+        super.setInitialSurfaceControlProperties(b);
+        if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
+            getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
+        }
+    }
+
+    void updateTrustedOverlay() {
+        mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
+                isWindowTrustedOverlay());
+        mInputWindowHandle.forceChange();
+    }
+
+    boolean isWindowTrustedOverlay() {
         return InputMonitor.isTrustedOverlay(mAttrs.type)
                 || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
                         && mSession.mCanAddInternalSystemWindow)
@@ -1332,6 +1347,11 @@
             windowFrames.setContentChanged(true);
         }
 
+        if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)
+                || !windowFrames.mRelFrame.equals(windowFrames.mLastRelFrame)) {
+            mWmService.mFrameChangingWindows.add(this);
+        }
+
         if (mAttrs.type == TYPE_DOCK_DIVIDER) {
             if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
                 mMovedByResize = true;
@@ -2755,12 +2775,7 @@
                 // bounds, as they would be used to display the dim layer.
                 final TaskFragment taskFragment = getTaskFragment();
                 if (taskFragment != null) {
-                    final Task task = taskFragment.asTask();
-                    if (task != null) {
-                        task.getDimBounds(mTmpRect);
-                    } else {
-                        mTmpRect.set(taskFragment.getBounds());
-                    }
+                    taskFragment.getDimBounds(mTmpRect);
                 } else if (getRootTask() != null) {
                     getRootTask().getDimBounds(mTmpRect);
                 }
@@ -3707,10 +3722,6 @@
         mDragResizingChangeReported = true;
         mWindowFrames.clearReportResizeHints();
 
-        // We update mLastFrame always rather than in the conditional with the last inset
-        // variables, because mFrameSizeChanged only tracks the width and height changing.
-        updateLastFrames();
-
         final int prevRotation = mLastReportedConfiguration
                 .getMergedConfiguration().windowConfiguration.getRotation();
         fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
@@ -3732,30 +3743,44 @@
 
         markRedrawForSyncReported();
 
-        try {
-            mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
-                    getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
-                    syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
-            if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
-                    .getMergedConfiguration().windowConfiguration.getRotation()) {
-                mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Requested redraw for orientation change: %s", this);
+        if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+            getProcess().scheduleClientTransactionItem(
+                    WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
+                            mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
+                            alwaysConsumeSystemBars, displayId,
+                            syncWithBuffers ? mSyncSeqId : -1, isDragResizing));
+            onResizePostDispatched(drawPending, prevRotation, displayId);
+        } else {
+            // TODO(b/301870955): cleanup after launch
+            try {
+                mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
+                        getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
+                        syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+                onResizePostDispatched(drawPending, prevRotation, displayId);
+            } catch (RemoteException e) {
+                // Cancel orientation change of this window to avoid blocking unfreeze display.
+                setOrientationChanging(false);
+                mLastFreezeDuration = (int) (SystemClock.elapsedRealtime()
+                        - mWmService.mDisplayFreezeTime);
+                Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
             }
-
-            if (mWmService.mAccessibilityController.hasCallbacks()) {
-                mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
-            }
-        } catch (RemoteException e) {
-            // Cancel orientation change of this window to avoid blocking unfreeze display.
-            setOrientationChanging(false);
-            mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
-                    - mWmService.mDisplayFreezeTime);
-            Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
         }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) {
+        if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
+                .getMergedConfiguration().windowConfiguration.getRotation()) {
+            mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "Requested redraw for orientation change: %s", this);
+        }
+
+        if (mWmService.mAccessibilityController.hasCallbacks()) {
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
+        }
+    }
+
     boolean inRelaunchingActivity() {
         return mActivityRecord != null && mActivityRecord.isRelaunching();
     }
@@ -5113,8 +5138,8 @@
 
     private void applyDims() {
         if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
-                && mToken.isVisibleRequested() && isVisibleNow() && !mHidden
-                && mTransitionController.canApplyDim(getTask())) {
+                && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow())
+                && !mHidden && mTransitionController.canApplyDim(getTask())) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
             // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
@@ -5124,7 +5149,13 @@
             mIsDimming = true;
             final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
             final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0;
-            getDimmer().dimBelow(this, dimAmount, blurRadius);
+            // If the window is visible from surface flinger perspective (mWinAnimator.getShown())
+            // but not window manager visible (!isVisibleNow()), it can still be the parent of the
+            // dim, but can not create a new surface or continue a dim alone.
+            if (isVisibleNow()) {
+                getDimmer().adjustAppearance(this, dimAmount, blurRadius);
+            }
+            getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */);
         }
     }
 
@@ -5184,14 +5215,16 @@
     void prepareSurfaces() {
         mIsDimming = false;
         if (mHasSurface) {
-            applyDims();
+            if (!Dimmer.DIMMER_REFACTOR) {
+                applyDims();
+            }
             updateSurfacePositionNonOrganized();
             // Send information to SurfaceFlinger about the priority of the current window.
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
             mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
-            if (surfaceTrustedOverlay()) {
-                getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
+            if (Dimmer.DIMMER_REFACTOR) {
+                applyDims();
             }
         }
         super.prepareSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e434f29..7d21dbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -569,6 +569,7 @@
                 && asActivityRecord() != null && isVisible()) {
             // Trigger an activity level rotation transition.
             mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
+            mTransitionController.collectVisibleChange(this);
             mTransitionController.setReady(this);
         }
         final int originalRotation = getWindowConfiguration().getRotation();
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index bc70658..24ee163 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -70,7 +70,7 @@
         "com_android_server_UsbHostManager.cpp",
         "com_android_server_vibrator_VibratorController.cpp",
         "com_android_server_vibrator_VibratorManagerService.cpp",
-        "com_android_server_PersistentDataBlockService.cpp",
+        "com_android_server_pdb_PersistentDataBlockService.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
         "com_android_server_sensor_SensorService.cpp",
@@ -189,7 +189,7 @@
         "android.hardware.thermal@1.0",
         "android.hardware.thermal-V1-ndk",
         "android.hardware.tv.input@1.0",
-        "android.hardware.tv.input-V1-ndk",
+        "android.hardware.tv.input-V2-ndk",
         "android.hardware.vibrator-V2-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
@@ -244,5 +244,5 @@
 
 filegroup {
     name: "lib_oomConnection_native",
-    srcs: ["com_android_server_am_OomConnection.cpp",],
+    srcs: ["com_android_server_am_OomConnection.cpp"],
 }
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 7e8ce60..0e45f61 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -20,6 +20,7 @@
 per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
 per-file com_android_server_locksettings_* = file:/services/core/java/com/android/server/locksettings/OWNERS
 per-file com_android_server_net_* = file:/services/core/java/com/android/server/net/OWNERS
+per-file com_android_server_pdb_* = file:/services/core/java/com/android/server/pdb/OWNERS
 per-file com_android_server_pm_* = file:/services/core/java/com/android/server/pm/OWNERS
 per-file com_android_server_power_* = file:/services/core/java/com/android/server/power/OWNERS
 per-file com_android_server_powerstats_* = file:/services/core/java/com/android/server/powerstats/OWNERS
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
deleted file mode 100644
index 97e69fb..0000000
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ /dev/null
@@ -1,120 +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.
- */
-
-#include <android_runtime/AndroidRuntime.h>
-#include <nativehelper/JNIHelp.h>
-#include <jni.h>
-#include <nativehelper/ScopedUtfChars.h>
-
-#include <utils/misc.h>
-#include <sys/ioctl.h>
-#include <sys/mount.h>
-#include <utils/Log.h>
-
-
-#include <inttypes.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-
-namespace android {
-
-    uint64_t get_block_device_size(int fd)
-    {
-        uint64_t size = 0;
-        int ret;
-
-        ret = ioctl(fd, BLKGETSIZE64, &size);
-
-        if (ret)
-            return 0;
-
-        return size;
-    }
-
-    int wipe_block_device(int fd)
-    {
-        uint64_t range[2];
-        int ret;
-        uint64_t len = get_block_device_size(fd);
-
-        range[0] = 0;
-        range[1] = len;
-
-        if (range[1] == 0)
-            return 0;
-
-        ret = ioctl(fd, BLKSECDISCARD, &range);
-        if (ret < 0) {
-            ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
-            range[0] = 0;
-            range[1] = len;
-            ret = ioctl(fd, BLKDISCARD, &range);
-            if (ret < 0) {
-                ALOGE("Discard failed: %s\n", strerror(errno));
-                return -1;
-            } else {
-                ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
-                return 0;
-            }
-
-        }
-
-        return ret;
-    }
-
-    static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
-    {
-        ScopedUtfChars path(env, jpath);
-        int fd = open(path.c_str(), O_RDONLY);
-
-        if (fd < 0)
-            return 0;
-
-        const uint64_t size = get_block_device_size(fd);
-
-        close(fd);
-
-        return size;
-    }
-
-    static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
-        ScopedUtfChars path(env, jpath);
-        int fd = open(path.c_str(), O_WRONLY);
-
-        if (fd < 0)
-            return 0;
-
-        const int ret = wipe_block_device(fd);
-
-        close(fd);
-
-        return ret;
-    }
-
-    static const JNINativeMethod sMethods[] = {
-         /* name, signature, funcPtr */
-        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
-        {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
-    };
-
-    int register_android_server_PersistentDataBlockService(JNIEnv* env)
-    {
-        return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService",
-                                        sMethods, NELEM(sMethods));
-    }
-
-} /* namespace android */
\ No newline at end of file
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
index b256f16..1844d30 100644
--- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -24,33 +24,33 @@
 #include "utils/Log.h"
 
 namespace android {
-static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds,
                                            jfloatArray jthresholds) {
-    if (juids == nullptr || jthresholds == nullptr) return;
+    if (jappIds == nullptr || jthresholds == nullptr) return;
 
-    ScopedIntArrayRO uids(env, juids);
+    ScopedIntArrayRO appIds(env, jappIds);
     ScopedFloatArrayRO thresholds(env, jthresholds);
 
-    if (uids.size() != thresholds.size()) {
-        ALOGE("uids size exceeds thresholds size!");
+    if (appIds.size() != thresholds.size()) {
+        ALOGE("appIds size exceeds thresholds size!");
         return;
     }
 
-    std::vector<int32_t> uidVector;
+    std::vector<int32_t> appIdVector;
     std::vector<float> thresholdVector;
-    size_t size = uids.size();
-    uidVector.reserve(size);
+    size_t size = appIds.size();
+    appIdVector.reserve(size);
     thresholdVector.reserve(size);
     for (int i = 0; i < size; i++) {
-        uidVector.push_back(static_cast<int32_t>(uids[i]));
+        appIdVector.push_back(static_cast<int32_t>(appIds[i]));
         thresholdVector.push_back(static_cast<float>(thresholds[i]));
     }
-    SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+    SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector);
 }
 
-static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId,
                                                  jfloat threshold) {
-    SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+    SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold);
 }
 
 static const JNINativeMethod gMethods[] = {
diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
new file mode 100644
index 0000000..1e3cfd0
--- /dev/null
+++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#include <android_runtime/AndroidRuntime.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+namespace android {
+
+uint64_t get_block_device_size(int fd) {
+    uint64_t size = 0;
+    int ret;
+
+    ret = ioctl(fd, BLKGETSIZE64, &size);
+
+    if (ret) return 0;
+
+    return size;
+}
+
+int wipe_block_device(int fd) {
+    uint64_t range[2];
+    int ret;
+    uint64_t len = get_block_device_size(fd);
+
+    range[0] = 0;
+    range[1] = len;
+
+    if (range[1] == 0) return 0;
+
+    ret = ioctl(fd, BLKSECDISCARD, &range);
+    if (ret < 0) {
+        ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
+        range[0] = 0;
+        range[1] = len;
+        ret = ioctl(fd, BLKDISCARD, &range);
+        if (ret < 0) {
+            ALOGE("Discard failed: %s\n", strerror(errno));
+            return -1;
+        } else {
+            ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+            return 0;
+        }
+    }
+
+    return ret;
+}
+
+static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env,
+                                                                                  jclass,
+                                                                                  jstring jpath) {
+    ScopedUtfChars path(env, jpath);
+    int fd = open(path.c_str(), O_RDONLY);
+
+    if (fd < 0) return 0;
+
+    const uint64_t size = get_block_device_size(fd);
+
+    close(fd);
+
+    return size;
+}
+
+static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass,
+                                                                  jstring jpath) {
+    ScopedUtfChars path(env, jpath);
+    int fd = open(path.c_str(), O_WRONLY);
+
+    if (fd < 0) return 0;
+
+    const int ret = wipe_block_device(fd);
+
+    close(fd);
+
+    return ret;
+}
+
+static const JNINativeMethod sMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J",
+         (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize},
+        {"nativeWipe", "(Ljava/lang/String;)I",
+         (void *)com_android_server_pdb_PersistentDataBlockService_wipe},
+};
+
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) {
+    return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService",
+                                    sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
\ No newline at end of file
diff --git a/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp b/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp
index a6084ea..cac13eb 100644
--- a/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp
+++ b/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp
@@ -34,9 +34,12 @@
 
 static constexpr uint64_t NSEC_PER_MSEC = 1000000;
 
-static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> &times,
-                           ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
-                           jlongArray tempForUidStats);
+static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> &times,
+                   jlongArray outArray);
+
+static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> &times,
+                            ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
+                            jlongArray outBrackets);
 
 static bool initialized = false;
 static jclass class_KernelCpuStatsCallback;
@@ -62,25 +65,43 @@
     return OK;
 }
 
+static jboolean nativeIsSupportedFeature(JNIEnv *env) {
+    if (!android::bpf::startTrackingUidTimes()) {
+        return false;
+    }
+    auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes();
+    return totalByScalingStep.has_value();
+}
+
 static jlong nativeReadCpuStats(JNIEnv *env, [[maybe_unused]] jobject zis, jobject callback,
                                 jintArray scalingStepToPowerBracketMap,
-                                jlong lastUpdateTimestampNanos, jlongArray tempForUidStats) {
+                                jlong lastUpdateTimestampNanos, jlongArray cpuTimeByScalingStep,
+                                jlongArray tempForUidStats) {
+    ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap);
+
     if (!initialized) {
         if (init(env) == EXCEPTION) {
             return 0L;
         }
     }
 
+    auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes();
+    if (!totalByScalingStep) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Unsupported kernel feature");
+        return EXCEPTION;
+    }
+
+    if (flatten(env, *totalByScalingStep, cpuTimeByScalingStep) == EXCEPTION) {
+        return 0L;
+    }
+
     uint64_t newLastUpdate = lastUpdateTimestampNanos;
     auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
     if (!data.has_value()) return lastUpdateTimestampNanos;
 
-    ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap);
-
     for (auto &[uid, times] : *data) {
-        int status =
-                extractUidStats(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats);
-        if (status == EXCEPTION) {
+        if (combineByBracket(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats) ==
+            EXCEPTION) {
             return 0L;
         }
         env->CallVoidMethod(callback, method_KernelCpuStatsCallback_processUidStats, (jint)uid,
@@ -89,13 +110,34 @@
     return newLastUpdate;
 }
 
-static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> &times,
-                           ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
-                           jlongArray tempForUidStats) {
-    ScopedLongArrayRW scopedTempForStats(env, tempForUidStats);
-    uint64_t *arrayForStats = reinterpret_cast<uint64_t *>(scopedTempForStats.get());
-    const uint8_t statsSize = scopedTempForStats.size();
-    memset(arrayForStats, 0, statsSize * sizeof(uint64_t));
+static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> &times,
+                   jlongArray outArray) {
+    ScopedLongArrayRW scopedOutArray(env, outArray);
+    const uint8_t scalingStepCount = scopedOutArray.size();
+    uint64_t *out = reinterpret_cast<uint64_t *>(scopedOutArray.get());
+    uint32_t scalingStep = 0;
+    for (const auto &subVec : times) {
+        for (uint32_t i = 0; i < subVec.size(); ++i) {
+            if (scalingStep >= scalingStepCount) {
+                jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
+                                     "Array is too short, size=%u, scalingStep=%u",
+                                     scalingStepCount, scalingStep);
+                return EXCEPTION;
+            }
+            out[scalingStep] = subVec[i] / NSEC_PER_MSEC;
+            scalingStep++;
+        }
+    }
+    return OK;
+}
+
+static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> &times,
+                            ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
+                            jlongArray outBrackets) {
+    ScopedLongArrayRW scopedOutBrackets(env, outBrackets);
+    uint64_t *brackets = reinterpret_cast<uint64_t *>(scopedOutBrackets.get());
+    const uint8_t statsSize = scopedOutBrackets.size();
+    memset(brackets, 0, statsSize * sizeof(uint64_t));
     const uint8_t scalingStepCount = scopedScalingStepToPowerBracketMap.size();
 
     uint32_t scalingStep = 0;
@@ -108,14 +150,14 @@
                                      scalingStepCount, scalingStep);
                 return EXCEPTION;
             }
-            uint32_t bucket = scopedScalingStepToPowerBracketMap[scalingStep];
-            if (bucket >= statsSize) {
+            uint32_t bracket = scopedScalingStepToPowerBracketMap[scalingStep];
+            if (bracket >= statsSize) {
                 jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
-                                     "UidStats array is too short, length=%u, bucket[%u]=%u",
-                                     statsSize, scalingStep, bucket);
+                                     "Bracket array is too short, length=%u, bracket[%u]=%u",
+                                     statsSize, scalingStep, bracket);
                 return EXCEPTION;
             }
-            arrayForStats[bucket] += subVec[i] / NSEC_PER_MSEC;
+            brackets[bracket] += subVec[i] / NSEC_PER_MSEC;
             scalingStep++;
         }
     }
@@ -123,7 +165,8 @@
 }
 
 static const JNINativeMethod method_table[] = {
-        {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J)J",
+        {"nativeIsSupportedFeature", "()Z", (void *)nativeIsSupportedFeature},
+        {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J[J)J",
          (void *)nativeReadCpuStats},
 };
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df44895..11734da 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -47,7 +47,7 @@
 int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
 int register_android_server_tv_TvUinputBridge(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
-int register_android_server_PersistentDataBlockService(JNIEnv* env);
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env);
 int register_android_server_Watchdog(JNIEnv* env);
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
@@ -108,7 +108,7 @@
     register_android_server_BatteryStatsService(env);
     register_android_server_tv_TvUinputBridge(env);
     register_android_server_tv_TvInputHal(env);
-    register_android_server_PersistentDataBlockService(env);
+    register_android_server_pdb_PersistentDataBlockService(env);
     register_android_server_HardwarePropertiesManagerService(env);
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 68e2c9a..dc05462 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -147,7 +147,6 @@
 }
 
 int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) {
-    Mutex::Autolock autoLock(&mLock);
     if (!mTvInput->setTvMessageEnabled(deviceId, streamId,
                                        static_cast<AidlTvMessageEventType>(type), enabled)
                  .isOk()) {
@@ -188,7 +187,7 @@
 
 void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         mConnections.add(info.deviceId, KeyedVector<int, Connection>());
     }
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -275,7 +274,7 @@
 
 void JTvInputHal::onDeviceUnavailable(int deviceId) {
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
         for (size_t i = 0; i < connections.size(); ++i) {
             removeStream(deviceId, connections.keyAt(i));
@@ -289,7 +288,7 @@
 
 void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
         for (size_t i = 0; i < connections.size(); ++i) {
             removeStream(deviceId, connections.keyAt(i));
@@ -330,7 +329,7 @@
 void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
     sp<BufferProducerThread> thread;
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
         Connection& connection = connections.editValueFor(streamId);
         if (connection.mThread == NULL) {
@@ -369,12 +368,20 @@
 }
 
 JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper(
-        const AidlTvMessageEvent& aidlTvMessageEvent) {
+        const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) {
+    auto messageList = aidlTvMessageEvent.messages;
     TvMessageEventWrapper event;
-    event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1,
-                          std::end(aidlTvMessageEvent.messages));
+    // Handle backwards compatibility for V1
+    if (isLegacyMessage) {
+        event.deviceId = messageList[0].groupId;
+        event.messages.insert(event.messages.begin(), std::begin(messageList) + 1,
+                              std::end(messageList));
+    } else {
+        event.deviceId = aidlTvMessageEvent.deviceId;
+        event.messages.insert(event.messages.begin(), std::begin(messageList),
+                              std::end(messageList));
+    }
     event.streamId = aidlTvMessageEvent.streamId;
-    event.deviceId = aidlTvMessageEvent.messages[0].groupId;
     event.type = aidlTvMessageEvent.type;
     return event;
 }
@@ -450,15 +457,30 @@
 ::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
         const AidlTvMessageEvent& event) {
     const std::string DEVICE_ID_SUBTYPE = "device_id";
-    if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
-        mHal->mLooper
-                ->sendMessage(new NotifyTvMessageHandler(mHal,
-                                                         TvMessageEventWrapper::createEventWrapper(
-                                                                 event)),
-                              static_cast<int>(event.type));
+    ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok();
+    int32_t aidlVersion = 0;
+    if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) {
+        bool validLegacyMessage = aidlVersion == 1 &&
+                event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1;
+        bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0;
+        if (validLegacyMessage || validTvMessage) {
+            mHal->mLooper->sendMessage(
+                    new NotifyTvMessageHandler(mHal,
+                                               TvMessageEventWrapper::
+                                                       createEventWrapper(event,
+                                                                          validLegacyMessage)),
+                    static_cast<int>(event.type));
+        } else {
+            status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion);
+        }
+    } else {
+        status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not "
+              "be inferred.",
+              aidlVersion);
     }
-
-    return ::ndk::ScopedAStatus::ok();
+    return status;
 }
 
 JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
@@ -522,4 +544,12 @@
     }
 }
 
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) {
+    if (mIsHidl) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    } else {
+        return mAidlTvInput->getInterfaceVersion(_aidl_return);
+    }
+}
+
 } // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index b7b4b16..6026a10 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -138,7 +138,7 @@
         TvMessageEventWrapper() {}
 
         static TvMessageEventWrapper createEventWrapper(
-                const AidlTvMessageEvent& aidlTvMessageEvent);
+                const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage);
 
         int streamId;
         int deviceId;
@@ -195,6 +195,7 @@
         ::ndk::ScopedAStatus getTvMessageQueueDesc(
                 MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
                 int32_t in_streamId);
+        ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);
 
     private:
         ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
@@ -220,7 +221,6 @@
     void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type,
                      AidlTvMessage& message, signed char data[], int dataLength);
 
-    Mutex mLock;
     Mutex mStreamLock;
     jweak mThiz;
     sp<Looper> mLooper;
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 debd891..215934f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -272,12 +272,12 @@
         <xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
             <xs:annotation name="final"/>
         </xs:element>
-        <!-- Animation time for brightness increase in millis -->
-        <xs:element  name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+        <!-- Animation speed for brightness increase. In framework brightness units per second. -->
+        <xs:element  name="screenBrightnessRampIncrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
-        <!-- Animation time for brightness decrease in millis -->
-        <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+        <!-- Animation speed for brightness decrease. In framework brightness units per second. -->
+        <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
     </xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2d27f0c..f7e0043 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -179,15 +179,15 @@
   public class HdrBrightnessConfig {
     ctor public HdrBrightnessConfig();
     method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
-    method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
     method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
-    method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+    method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
+    method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
     method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
-    method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
     method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
-    method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
     method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+    method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
+    method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
   }
 
   public class HighBrightnessMode {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 08df651..a4adf58 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -65,7 +65,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.infra.AbstractMasterSystemService;
@@ -90,7 +89,7 @@
  */
 public final class CredentialManagerService
         extends AbstractMasterSystemService<
-                CredentialManagerService, CredentialManagerServiceImpl> {
+        CredentialManagerService, CredentialManagerServiceImpl> {
 
     private static final String TAG = "CredManSysService";
     private static final String PERMISSION_DENIED_ERROR = "permission_denied";
@@ -111,7 +110,8 @@
 
     /** Cache of all ongoing request sessions per user id. */
     @GuardedBy("mLock")
-    private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = new SparseArray<>();
+    private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions =
+            new SparseArray<>();
 
     private final SessionManager mSessionManager = new SessionManager();
 
@@ -123,8 +123,6 @@
                 null,
                 PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
         mContext = context;
-
-        mPackageMonitor.register(context, context.getMainLooper(), false);
     }
 
     @NonNull
@@ -141,7 +139,8 @@
         serviceInfos.forEach(
                 info -> {
                     services.add(
-                            new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
+                            new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
+                                    info));
                 });
         return services;
     }
@@ -217,8 +216,8 @@
         for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) {
             removeServiceFromCache(serviceToBeRemoved, userId);
             removeServiceFromSystemServicesCache(serviceToBeRemoved, userId);
-            removeServiceFromMultiModeSettings(
-                    serviceToBeRemoved.getComponentName().flattenToString(), userId);
+            removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName()
+                    .flattenToString(), userId);
             CredentialDescriptionRegistry.forUser(userId)
                     .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
         }
@@ -287,20 +286,13 @@
     }
 
     private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
-        final int resolvedUserId =
-                ActivityManager.handleIncomingUser(
-                        Binder.getCallingPid(),
-                        Binder.getCallingUid(),
-                        userId,
-                        false,
-                        false,
-                        "getPrimaryProvidersForUserId",
-                        null);
-        SecureSettingsServiceNameResolver resolver =
-                new SecureSettingsServiceNameResolver(
-                        context,
-                        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
-                        /* isMultipleMode= */ true);
+        final int resolvedUserId = ActivityManager.handleIncomingUser(
+                Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, false,
+                "getPrimaryProvidersForUserId", null);
+        SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver(
+                context, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                /* isMultipleMode= */ true);
         String[] serviceNames = resolver.readServiceNameList(resolvedUserId);
         if (serviceNames == null) {
             return new HashSet<ComponentName>();
@@ -337,8 +329,7 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             return DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_CREDENTIAL,
-                    DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
+                    DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
                     false);
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -354,14 +345,13 @@
         List<ProviderSession> providerSessions = new ArrayList<>();
         for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
                 activeCredentialContainers) {
-            ProviderSession providerSession =
-                    ProviderRegistryGetSession.createNewSession(
-                            mContext,
-                            UserHandle.getCallingUserId(),
-                            session,
-                            session.mClientAppInfo,
-                            result.second.mPackageName,
-                            result.first);
+            ProviderSession providerSession = ProviderRegistryGetSession.createNewSession(
+                    mContext,
+                    UserHandle.getCallingUserId(),
+                    session,
+                    session.mClientAppInfo,
+                    result.second.mPackageName,
+                    result.first);
             providerSessions.add(providerSession);
             session.addProviderSession(providerSession.getComponentName(), providerSession);
         }
@@ -377,23 +367,23 @@
         List<ProviderSession> providerSessions = new ArrayList<>();
         for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
                 activeCredentialContainers) {
-            ProviderSession providerSession =
-                    ProviderRegistryGetSession.createNewSession(
-                            mContext,
-                            UserHandle.getCallingUserId(),
-                            session,
-                            session.mClientAppInfo,
-                            result.second.mPackageName,
-                            result.first);
+            ProviderSession providerSession = ProviderRegistryGetSession.createNewSession(
+                    mContext,
+                    UserHandle.getCallingUserId(),
+                    session,
+                    session.mClientAppInfo,
+                    result.second.mPackageName,
+                    result.first);
             providerSessions.add(providerSession);
             session.addProviderSession(providerSession.getComponentName(), providerSession);
         }
         return providerSessions;
     }
 
+
     @NonNull
     private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
-            getFilteredResultFromRegistry(List<CredentialOption> options) {
+    getFilteredResultFromRegistry(List<CredentialOption> options) {
         // Session for active/provisioned credential descriptions;
         CredentialDescriptionRegistry registry =
                 CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
@@ -403,12 +393,10 @@
                 options.stream()
                         .map(
                                 getCredentialOption ->
-                                        new HashSet<>(
-                                                getCredentialOption
-                                                        .getCredentialRetrievalData()
-                                                        .getStringArrayList(
-                                                                CredentialOption
-                                                                        .SUPPORTED_ELEMENT_KEYS)))
+                                        new HashSet<>(getCredentialOption
+                                                .getCredentialRetrievalData()
+                                                .getStringArrayList(
+                                                        CredentialOption.SUPPORTED_ELEMENT_KEYS)))
                         .collect(Collectors.toSet());
 
         // All requested credential descriptions based on the given request.
@@ -420,14 +408,12 @@
 
         for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) {
             for (CredentialOption credentialOption : options) {
-                Set<String> requestedElementKeys =
-                        new HashSet<>(
-                                credentialOption
-                                        .getCredentialRetrievalData()
-                                        .getStringArrayList(
-                                                CredentialOption.SUPPORTED_ELEMENT_KEYS));
-                if (CredentialDescriptionRegistry.checkForMatch(
-                        filterResult.mElementKeys, requestedElementKeys)) {
+                Set<String> requestedElementKeys = new HashSet<>(
+                        credentialOption
+                                .getCredentialRetrievalData()
+                                .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
+                if (CredentialDescriptionRegistry.checkForMatch(filterResult.mElementKeys,
+                        requestedElementKeys)) {
                     result.add(new Pair<>(credentialOption, filterResult));
                 }
             }
@@ -463,7 +449,9 @@
     }
 
     private CallingAppInfo constructCallingAppInfo(
-            String realPackageName, int userId, @Nullable String origin) {
+            String realPackageName,
+            int userId,
+            @Nullable String origin) {
         final PackageInfo packageInfo;
         CallingAppInfo callingAppInfo;
         try {
@@ -489,7 +477,8 @@
                 GetCredentialRequest request,
                 IGetCandidateCredentialsCallback callback,
                 final String callingPackage) {
-            Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + callingPackage);
+            Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
+                    + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             final int userId = UserHandle.getCallingUserId();
@@ -507,7 +496,8 @@
                             request,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
                             getEnabledProvidersForUser(userId),
-                            CancellationSignal.fromTransport(cancelTransport));
+                            CancellationSignal.fromTransport(cancelTransport)
+                    );
             addSessionLocked(userId, session);
 
             List<ProviderSession> providerSessions =
@@ -541,7 +531,8 @@
                 IGetCredentialCallback callback,
                 final String callingPackage) {
             final long timestampBegan = System.nanoTime();
-            Slog.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
+            Slog.i(TAG, "starting executeGetCredential with callingPackage: "
+                    + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             final int userId = UserHandle.getCallingUserId();
@@ -566,7 +557,8 @@
                             timestampBegan);
             addSessionLocked(userId, session);
 
-            List<ProviderSession> providerSessions = prepareProviderSessions(request, session);
+            List<ProviderSession> providerSessions =
+                    prepareProviderSessions(request, session);
 
             if (providerSessions.isEmpty()) {
                 try {
@@ -625,17 +617,15 @@
             if (providerSessions.isEmpty()) {
                 try {
                     prepareGetCredentialCallback.onResponse(
-                            new PrepareGetCredentialResponseInternal(
-                                    PermissionUtils.hasPermission(
-                                            mContext,
-                                            callingPackage,
-                                            Manifest.permission
-                                                    .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS
-                                                    ),
-                                    /* credentialResultTypes= */ null,
-                                    /* hasAuthenticationResults= */ false,
-                                    /* hasRemoteResults= */ false,
-                                    /* pendingIntent= */ null));
+                            new PrepareGetCredentialResponseInternal(PermissionUtils.hasPermission(
+                                    mContext,
+                                    callingPackage,
+                                    Manifest.permission
+                                            .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS),
+                                    /*credentialResultTypes=*/null,
+                                    /*hasAuthenticationResults=*/false,
+                                    /*hasRemoteResults=*/false,
+                                    /*pendingIntent=*/null));
                 } catch (RemoteException e) {
                     Slog.e(
                             TAG,
@@ -651,32 +641,27 @@
         }
 
         private List<ProviderSession> prepareProviderSessions(
-                GetCredentialRequest request, GetRequestSession session) {
+                GetCredentialRequest request,
+                GetRequestSession session) {
             List<ProviderSession> providerSessions;
 
             if (isCredentialDescriptionApiEnabled()) {
                 List<CredentialOption> optionsThatRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        credentialOption ->
-                                                credentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getStringArrayList(
-                                                                        CredentialOption
-                                                                        .SUPPORTED_ELEMENT_KEYS)
-                                                        != null)
+                                .filter(credentialOption -> credentialOption
+                                        .getCredentialRetrievalData()
+                                        .getStringArrayList(
+                                                CredentialOption
+                                                        .SUPPORTED_ELEMENT_KEYS) != null)
                                 .toList();
 
                 List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        credentialOption ->
-                                                credentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getStringArrayList(
-                                                                        CredentialOption
-                                                                        .SUPPORTED_ELEMENT_KEYS)
-                                                        == null)
+                                .filter(credentialOption -> credentialOption
+                                        .getCredentialRetrievalData()
+                                        .getStringArrayList(
+                                                CredentialOption
+                                                        .SUPPORTED_ELEMENT_KEYS) == null)
                                 .toList();
 
                 List<ProviderSession> sessionsWithoutRemoteService =
@@ -721,7 +706,8 @@
                 ICreateCredentialCallback callback,
                 String callingPackage) {
             final long timestampBegan = System.nanoTime();
-            Slog.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
+            Slog.i(TAG, "starting executeCreateCredential with callingPackage: "
+                    + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             if (request.getOrigin() != null) {
@@ -770,8 +756,8 @@
                 } catch (RemoteException e) {
                     Slog.e(
                             TAG,
-                            "Issue invoking onError on ICreateCredentialCallback " + "callback: ",
-                            e);
+                            "Issue invoking onError on ICreateCredentialCallback "
+                                    + "callback: ", e);
                 }
             }
 
@@ -784,8 +770,8 @@
             try {
                 var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
                 initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
-                MetricUtilities.logApiCalledInitialPhase(
-                        initMetric, session.mRequestSessionMetric.returnIncrementSequence());
+                MetricUtilities.logApiCalledInitialPhase(initMetric,
+                        session.mRequestSessionMetric.returnIncrementSequence());
             } catch (Exception e) {
                 Slog.i(TAG, "Unexpected error during metric logging: ", e);
             }
@@ -793,32 +779,25 @@
 
         @Override
         public void setEnabledProviders(
-                List<String> primaryProviders,
-                List<String> providers,
-                int userId,
+                List<String> primaryProviders, List<String> providers, int userId,
                 ISetEnabledProvidersCallback callback) {
             final int callingUid = Binder.getCallingUid();
             if (!hasWriteSecureSettingsPermission()) {
                 try {
                     MetricUtilities.logApiCalledSimpleV2(
-                            ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
+                            ApiName.SET_ENABLED_PROVIDERS,
+                            ApiStatus.FAILURE, callingUid);
                     callback.onError(
                             PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
                 } catch (RemoteException e) {
                     MetricUtilities.logApiCalledSimpleV2(
-                            ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
+                            ApiName.SET_ENABLED_PROVIDERS,
+                            ApiStatus.FAILURE, callingUid);
                     Slog.e(TAG, "Issue with invoking response: ", e);
                 }
                 return;
             }
 
-            // If we don't have any primary providers enabled anymore then
-            // we should erase all the providers since the feature is
-            // now disabled.
-            if (primaryProviders.isEmpty()) {
-                providers.clear();
-            }
-
             userId =
                     ActivityManager.handleIncomingUser(
                             Binder.getCallingPid(),
@@ -829,19 +808,17 @@
                             "setEnabledProviders",
                             null);
 
-            Set<String> enabledProviders = new HashSet<>(providers);
-            enabledProviders.addAll(primaryProviders);
+            Set<String> enableProvider = new HashSet<>(providers);
+            enableProvider.addAll(primaryProviders);
 
             boolean writeEnabledStatus =
-                    Settings.Secure.putStringForUser(
-                            getContext().getContentResolver(),
+                    Settings.Secure.putStringForUser(getContext().getContentResolver(),
                             Settings.Secure.CREDENTIAL_SERVICE,
-                            String.join(":", enabledProviders),
+                            String.join(":", enableProvider),
                             userId);
 
             boolean writePrimaryStatus =
-                    Settings.Secure.putStringForUser(
-                            getContext().getContentResolver(),
+                    Settings.Secure.putStringForUser(getContext().getContentResolver(),
                             Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                             String.join(":", primaryProviders),
                             userId);
@@ -850,13 +827,15 @@
                 Slog.e(TAG, "Failed to store setting containing enabled or primary providers");
                 try {
                     MetricUtilities.logApiCalledSimpleV2(
-                            ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
+                            ApiName.SET_ENABLED_PROVIDERS,
+                            ApiStatus.FAILURE, callingUid);
                     callback.onError(
                             "failed_setting_store",
                             "Failed to store setting containing enabled or primary providers");
                 } catch (RemoteException e) {
                     MetricUtilities.logApiCalledSimpleV2(
-                            ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
+                            ApiName.SET_ENABLED_PROVIDERS,
+                            ApiStatus.FAILURE, callingUid);
                     Slog.e(TAG, "Issue with invoking error response: ", e);
                     return;
                 }
@@ -865,11 +844,13 @@
             // Call the callback.
             try {
                 MetricUtilities.logApiCalledSimpleV2(
-                        ApiName.SET_ENABLED_PROVIDERS, ApiStatus.SUCCESS, callingUid);
+                        ApiName.SET_ENABLED_PROVIDERS,
+                        ApiStatus.SUCCESS, callingUid);
                 callback.onResponse();
             } catch (RemoteException e) {
                 MetricUtilities.logApiCalledSimpleV2(
-                        ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
+                        ApiName.SET_ENABLED_PROVIDERS,
+                        ApiStatus.FAILURE, callingUid);
                 Slog.e(TAG, "Issue with invoking response: ", e);
                 // TODO: Propagate failure
             }
@@ -878,10 +859,8 @@
         @Override
         public boolean isEnabledCredentialProviderService(
                 ComponentName componentName, String callingPackage) {
-            Slog.i(
-                    TAG,
-                    "isEnabledCredentialProviderService with componentName: "
-                            + componentName.flattenToString());
+            Slog.i(TAG, "isEnabledCredentialProviderService with componentName: "
+                    + componentName.flattenToString());
 
             // TODO(253157366): Check additional set of services.
             final int userId = UserHandle.getCallingUserId();
@@ -898,8 +877,7 @@
                             // The component name and the package name do not match.
                             MetricUtilities.logApiCalledSimpleV2(
                                     ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
-                                    ApiStatus.FAILURE,
-                                    callingUid);
+                                    ApiStatus.FAILURE, callingUid);
                             Slog.w(
                                     TAG,
                                     "isEnabledCredentialProviderService: Component name does "
@@ -908,8 +886,7 @@
                         }
                         MetricUtilities.logApiCalledSimpleV2(
                                 ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
-                                ApiStatus.SUCCESS,
-                                callingUid);
+                                ApiStatus.SUCCESS, callingUid);
                         return true;
                     }
                 }
@@ -924,14 +901,13 @@
             verifyGetProvidersPermission();
             final int callingUid = Binder.getCallingUid();
             MetricUtilities.logApiCalledSimpleV2(
-                    ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, ApiStatus.SUCCESS, callingUid);
+                    ApiName.GET_CREDENTIAL_PROVIDER_SERVICES,
+                    ApiStatus.SUCCESS, callingUid);
+            return CredentialProviderInfoFactory
+                    .getCredentialProviderServices(
+                            mContext, userId, providerFilter, getEnabledProvidersForUser(userId),
+                            getPrimaryProvidersForUserId(mContext, userId));
 
-            return CredentialProviderInfoFactory.getCredentialProviderServices(
-                    mContext,
-                    userId,
-                    providerFilter,
-                    getEnabledProvidersForUser(userId),
-                    getPrimaryProvidersForUserId(mContext, userId));
         }
 
         @Override
@@ -941,10 +917,7 @@
 
             final int userId = UserHandle.getCallingUserId();
             return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
-                    mContext,
-                    userId,
-                    providerFilter,
-                    getEnabledProvidersForUser(userId),
+                    mContext, userId, providerFilter, getEnabledProvidersForUser(userId),
                     getPrimaryProvidersForUserId(mContext, userId));
         }
 
@@ -962,22 +935,15 @@
         }
 
         private Set<ComponentName> getEnabledProvidersForUser(int userId) {
-            final int resolvedUserId =
-                    ActivityManager.handleIncomingUser(
-                            Binder.getCallingPid(),
-                            Binder.getCallingUid(),
-                            userId,
-                            false,
-                            false,
-                            "getEnabledProvidersForUser",
-                            null);
+            final int resolvedUserId = ActivityManager.handleIncomingUser(
+                    Binder.getCallingPid(), Binder.getCallingUid(),
+                    userId, false, false,
+                    "getEnabledProvidersForUser", null);
 
             Set<ComponentName> enabledProviders = new HashSet<>();
-            String directValue =
-                    Settings.Secure.getStringForUser(
-                            mContext.getContentResolver(),
-                            Settings.Secure.CREDENTIAL_SERVICE,
-                            resolvedUserId);
+            String directValue = Settings.Secure.getStringForUser(
+                    mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE,
+                    resolvedUserId);
 
             if (!TextUtils.isEmpty(directValue)) {
                 String[] components = directValue.split(":");
@@ -998,7 +964,8 @@
                 IClearCredentialStateCallback callback,
                 String callingPackage) {
             final long timestampBegan = System.nanoTime();
-            Slog.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
+            Slog.i(TAG, "starting clearCredentialState with callingPackage: "
+                    + callingPackage);
             final int userId = UserHandle.getCallingUserId();
             int callingUid = Binder.getCallingUid();
             enforceCallingPackage(callingPackage, callingUid);
@@ -1029,13 +996,13 @@
             if (providerSessions.isEmpty()) {
                 try {
                     // TODO("Replace with properly defined error type")
-                    callback.onError("UNKNOWN", "No credentials available on " + "this device");
+                    callback.onError("UNKNOWN", "No credentials available on "
+                            + "this device");
                 } catch (RemoteException e) {
                     Slog.e(
                             TAG,
                             "Issue invoking onError on IClearCredentialStateCallback "
-                                    + "callback: ",
-                            e);
+                                    + "callback: ", e);
                 }
             }
 
@@ -1068,7 +1035,9 @@
         public void unregisterCredentialDescription(
                 UnregisterCredentialDescriptionRequest request, String callingPackage)
                 throws IllegalArgumentException {
-            Slog.i(TAG, "unregisterCredentialDescription with callingPackage: " + callingPackage);
+            Slog.i(TAG, "unregisterCredentialDescription with callingPackage: "
+                    + callingPackage);
+
 
             if (!isCredentialDescriptionApiEnabled()) {
                 throw new UnsupportedOperationException("Feature not supported");
@@ -1092,18 +1061,18 @@
     }
 
     private void enforcePermissionForAllowedProviders(GetCredentialRequest request) {
-        boolean containsAllowedProviders =
-                request.getCredentialOptions().stream()
-                        .anyMatch(
-                                option ->
-                                        option.getAllowedProviders() != null
-                                                && !option.getAllowedProviders().isEmpty());
+        boolean containsAllowedProviders = request.getCredentialOptions()
+                .stream()
+                .anyMatch(option -> option.getAllowedProviders() != null
+                        && !option.getAllowedProviders().isEmpty());
         if (containsAllowedProviders) {
-            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, null);
+            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS,
+                    null);
         }
     }
 
-    private void addSessionLocked(@UserIdInt int userId, RequestSession requestSession) {
+    private void addSessionLocked(@UserIdInt int userId,
+            RequestSession requestSession) {
         synchronized (mLock) {
             mSessionManager.addSession(userId, requestSession.mRequestId, requestSession);
         }
@@ -1111,11 +1080,11 @@
 
     private void enforceCallingPackage(String callingPackage, int callingUid) {
         int packageUid;
-        PackageManager pm =
-                mContext.createContextAsUser(UserHandle.getUserHandleForUid(callingUid), 0)
-                        .getPackageManager();
+        PackageManager pm = mContext.createContextAsUser(
+                UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
         try {
-            packageUid = pm.getPackageUid(callingPackage, PackageManager.PackageInfoFlags.of(0));
+            packageUid = pm.getPackageUid(callingPackage,
+                    PackageManager.PackageInfoFlags.of(0));
         } catch (PackageManager.NameNotFoundException e) {
             throw new SecurityException(callingPackage + " not found");
         }
@@ -1141,72 +1110,4 @@
             mRequestSessions.get(userId).put(token, requestSession);
         }
     }
-
-    /** Updates settings when packages are removed. */
-    private final PackageMonitor mPackageMonitor =
-            new PackageMonitor() {
-                
-                @Override
-                public void onPackageRemoved(String packageName, int uid) {
-                    Slog.d(TAG, "onPackageRemoved: " + packageName);
-
-                    // Remove any providers from the primary setting that contain the package name
-                    // being removed.
-                    Set<String> primaryProviders =
-                            getStoredProviders(
-                                    Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, packageName);
-                    if (!Settings.Secure.putString(
-                            getContext().getContentResolver(),
-                            Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
-                            String.join(":", primaryProviders))) {
-                        Slog.w(TAG, "Failed to remove primary package: " + packageName);
-                        return;
-                    }
-
-                    // Get the secondary providers and if there are no primary providers then
-                    // we should erase all the providers from the secondary list because the
-                    // feature is now disabled.
-                    if (!primaryProviders.isEmpty()) {
-                        return;
-                    }
-
-                    if (!Settings.Secure.putString(
-                            getContext().getContentResolver(),
-                            Settings.Secure.CREDENTIAL_SERVICE,
-                            "")) {
-                        Slog.w(TAG, "Failed to remove secondary package: " + packageName);
-                        return;
-                    }
-                }
-
-                private Set<String> getStoredProviders(String key, String packageName) {
-                    // Get the current providers.
-                    String rawProviders =
-                            Settings.Secure.getStringForUser(
-                                getContext().getContentResolver(), key,
-                                UserHandle.myUserId());
-                    if (rawProviders == null) {
-                        Slog.w(TAG, "settings key is null: " + key);
-                        return new HashSet<>();
-                    }
-
-                    // If the app being removed matches any of the package names from
-                    // this list then don't add it in the output.
-                    Set<String> providers = new HashSet<>();
-                    for (String rawComponentName : rawProviders.split(":")) {
-                        if (TextUtils.isEmpty(rawComponentName)
-                                || rawComponentName.equals("null")) {
-                            Slog.d(TAG, "provider component name is empty or null");
-                            continue;
-                        }
-
-                        ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
-                        if (cn != null && !cn.getPackageName().equals(packageName)) {
-                            providers.add(cn.flattenToString());
-                        }
-                    }
-
-                    return providers;
-                }
-            };
 }
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 41706f0..8dfa685 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -23,8 +23,6 @@
         "services.core",
         "app-compat-annotations",
         "service-permission.stubs.system_server",
-    ],
-    static_libs: [
         "device_policy_aconfig_flags_lib",
     ],
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 25e8475..323d387 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -24,6 +24,7 @@
 import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
 import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
 import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
+import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
 
 import android.Manifest;
@@ -65,7 +66,6 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.utils.Slogf;
 
 import libcore.io.IoUtils;
@@ -159,7 +159,7 @@
 
         synchronized (mLock) {
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
-            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+            if (devicePolicySizeTrackingEnabled()) {
                 if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
                         policyDefinition, userId)) {
                     return;
@@ -282,7 +282,7 @@
             }
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
-            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+            if (devicePolicySizeTrackingEnabled()) {
                 decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
             }
 
@@ -428,7 +428,7 @@
 
         synchronized (mLock) {
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+            if (devicePolicySizeTrackingEnabled()) {
                 if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
                         policyDefinition, UserHandle.USER_ALL)) {
                     return;
@@ -499,7 +499,7 @@
         synchronized (mLock) {
             PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
 
-            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+            if (devicePolicySizeTrackingEnabled()) {
                 decreasePolicySizeForAdmin(policyState, enforcingAdmin);
             }
 
@@ -1781,7 +1781,7 @@
 
         private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+            if (devicePolicySizeTrackingEnabled()) {
                 if (mAdminPolicySize != null) {
                     for (int i = 0; i < mAdminPolicySize.size(); i++) {
                         int userId = mAdminPolicySize.keyAt(i);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 43e47d7..5f2d87c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -219,6 +219,7 @@
 import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
 import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
 import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -486,14 +487,13 @@
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.DefaultCrossProfileIntentFilter;
 import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils;
 import com.android.server.pm.PackageManagerLocal;
@@ -872,17 +872,9 @@
             "enable_permission_based_access";
     private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
 
-    private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG =
-            "enable_device_policy_engine";
-    private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
-
     // TODO(b/265683382) remove the flag after rollout.
     public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
 
-    // TODO(b/261999445) remove the flag after rollout.
-    private static final String HEADLESS_FLAG = "headless";
-    private static final boolean DEFAULT_HEADLESS_FLAG = true;
-
     // TODO(b/266831522) remove the flag after rollout.
     private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
     private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
@@ -3438,7 +3430,7 @@
             }
 
             revertTransferOwnershipIfNecessaryLocked();
-            if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+            if (!policyEngineMigrationV2Enabled()) {
                 updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
             }
         }
@@ -4025,75 +4017,41 @@
     }
 
     private void clearDeviceOwnerUserRestriction(UserHandle userHandle) {
-        if (isHeadlessFlagEnabled()) {
-            for (int userId : mUserManagerInternal.getUserIds()) {
-                UserHandle user = UserHandle.of(userId);
-                // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the
-                // original state
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
-                            false, user);
-                }
-                // When a device owner is set, the system automatically restricts adding a
-                // managed profile.
-                // Remove this restriction when the device owner is cleared.
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                        user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                            false,
-                            user);
-                }
-                // When a device owner is set, the system automatically restricts adding a
-                // clone profile.
-                // Remove this restriction when the device owner is cleared.
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                            false, user);
-                }
-
-                // When a device owner is set, the system automatically restricts adding a
-                // private profile.
-                // Remove this restriction when the device owner is cleared.
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                        user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                            false, user);
-                }
-            }
-        } else {
-            // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
-                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
-                        userHandle);
+        for (int userId : mUserManagerInternal.getUserIds()) {
+            UserHandle user = UserHandle.of(userId);
+            // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the
+            // original state
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) {
+                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
+                        false, user);
             }
             // When a device owner is set, the system automatically restricts adding a
             // managed profile.
             // Remove this restriction when the device owner is cleared.
             if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                    userHandle)) {
+                    user)) {
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
                         false,
-                        userHandle);
+                        user);
             }
-            // When a device owner is set, the system automatically restricts adding a clone
-            // profile.
+            // When a device owner is set, the system automatically restricts adding a
+            // clone profile.
             // Remove this restriction when the device owner is cleared.
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                    userHandle)) {
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) {
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                        false,
-                        userHandle);
+                        false, user);
             }
 
             // When a device owner is set, the system automatically restricts adding a
             // private profile.
             // Remove this restriction when the device owner is cleared.
             if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                    userHandle)) {
+                    user)) {
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                        false, userHandle);
+                        false, user);
             }
         }
+
     }
 
     /**
@@ -6476,7 +6434,7 @@
                          KeyChain.bindAsUser(mContext, userHandle)) {
                 IKeyChainService keyChain = keyChainConnection.getService();
                 return keyChain.setGrant(granteeUid, alias, hasGrant);
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Setting grant for package.", e);
                 return false;
             }
@@ -7956,14 +7914,8 @@
                 hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE));
 
         synchronized (getLockObject()) {
-            // TODO(b/261999445): Remove
-            ActiveAdmin admin;
-            if (isHeadlessFlagEnabled()) {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                        UserHandle.USER_SYSTEM);
-            }
+            ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
+
             Preconditions.checkState(admin != null,
                     "Lost mode location updates can only be sent on an organization-owned device.");
             mInjector.binderWithCleanCallingIdentity(() -> {
@@ -9449,39 +9401,24 @@
                 // profile, such that the admin on that managed profile has extended management
                 // capabilities that can affect the entire device (but not access private data
                 // on the primary profile).
-                if (isHeadlessFlagEnabled()) {
-                    for (int u : mUserManagerInternal.getUserIds()) {
-                        mUserManager.setUserRestriction(
-                                UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
-                                UserHandle.of(u));
-                        // Restrict adding a clone profile when a device owner is set on the device.
-                        // That is to prevent the co-existence of a clone profile and a device owner
-                        // on the same device.
-                        // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
-                        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                                true,
-                                UserHandle.of(u));
-
-                        // Restrict adding a private profile when a device owner is set.
-                        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                                true,
-                                UserHandle.of(u));
-                    }
-                } else {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                            true,
-                            UserHandle.of(userId));
+                for (int u : mUserManagerInternal.getUserIds()) {
+                    mUserManager.setUserRestriction(
+                            UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
+                            UserHandle.of(u));
                     // Restrict adding a clone profile when a device owner is set on the device.
                     // That is to prevent the co-existence of a clone profile and a device owner
                     // on the same device.
                     // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
                             true,
-                            UserHandle.of(userId));
+                            UserHandle.of(u));
+
+                    // Restrict adding a private profile when a device owner is set.
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
                             true,
-                            UserHandle.of(userId));
+                            UserHandle.of(u));
                 }
+
                 // TODO Send to system too?
                 sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
             });
@@ -20119,14 +20056,8 @@
         synchronized (getLockObject()) {
             // Only DO or COPE PO can turn on CC mode, so take a shortcut here and only look at
             // their ActiveAdmin, instead of iterating through all admins.
-            ActiveAdmin admin;
-            // TODO(b/261999445): remove
-            if (isHeadlessFlagEnabled()) {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                        UserHandle.USER_SYSTEM);
-            }
+            ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
+
             return admin != null ? admin.mCommonCriteriaMode : false;
         }
     }
@@ -21393,7 +21324,7 @@
     }
 
     private void disallowAddUser() {
-        if (!isHeadlessFlagEnabled() || mIsAutomotive) {
+        if (mIsAutomotive) {
             // Auto still enables adding users due to the communal nature of those devices
             if (mInjector.userManagerIsHeadlessSystemUserMode()) {
                 Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
@@ -21640,7 +21571,7 @@
         Objects.requireNonNull(packageName, "Admin package name must be provided");
         final CallerIdentity caller = getCallerIdentity(packageName);
 
-        if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+        if (!policyEngineMigrationV2Enabled()) {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                     "USB data signaling can only be controlled by a device owner or "
@@ -21650,7 +21581,7 @@
         }
 
         synchronized (getLockObject()) {
-            if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+            if (policyEngineMigrationV2Enabled()) {
                 EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                         /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
                         caller.getPackageName(),
@@ -21690,7 +21621,7 @@
     @Override
     public boolean isUsbDataSignalingEnabled(String packageName) {
         final CallerIdentity caller = getCallerIdentity(packageName);
-        if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+        if (policyEngineMigrationV2Enabled()) {
             Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.USB_DATA_SIGNALING,
                     caller.getUserId());
@@ -21711,14 +21642,7 @@
     }
 
     private boolean isUsbDataSignalingEnabledInternalLocked() {
-        // TODO(b/261999445): remove
-        ActiveAdmin admin;
-        if (isHeadlessFlagEnabled()) {
-            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-        } else {
-            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                    UserHandle.USER_SYSTEM);
-        }
+        ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
         return admin == null || admin.mUsbDataSignalingEnabled;
     }
 
@@ -21785,14 +21709,7 @@
     @Override
     public int getMinimumRequiredWifiSecurityLevel() {
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            // TODO(b/261999445): remove
-            if (isHeadlessFlagEnabled()) {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                        UserHandle.USER_SYSTEM);
-            }
+            ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN
                     : admin.mWifiMinimumSecurityLevel;
         }
@@ -23169,16 +23086,8 @@
                             || isProfileOwnerOfOrganizationOwnedDevice(caller));
         }
         synchronized (getLockObject()) {
-            // TODO(b/261999445): Remove
-            ActiveAdmin admin;
-            if (isHeadlessFlagEnabled()) {
-                admin =
+            ActiveAdmin admin =
                         getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                                UserHandle.USER_SYSTEM);
-            }
 
             if (admin != null) {
                 final String memtagProperty = "arm64.memtag.bootctl";
@@ -23211,29 +23120,14 @@
                             || isSystemUid(caller));
         }
         synchronized (getLockObject()) {
-            // TODO(b/261999445): Remove
-            ActiveAdmin admin;
-            if (isHeadlessFlagEnabled()) {
-                admin =
+            ActiveAdmin admin =
                         getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                                UserHandle.USER_SYSTEM);
-            }
             return admin != null
                     ? admin.mtePolicy
                     : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
         }
     }
 
-    private boolean isHeadlessFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                HEADLESS_FLAG,
-                DEFAULT_HEADLESS_FLAG);
-    }
-
     @Override
     public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
         synchronized (getLockObject()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
deleted file mode 100644
index 1a45782..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-aconfig_declarations {
-    name: "device_policy_aconfig_flags",
-    package: "com.android.server.devicepolicy.flags",
-    srcs: [
-        "flags.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "device_policy_aconfig_flags_lib",
-    aconfig_declarations: "device_policy_aconfig_flags",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
deleted file mode 100644
index 7e17ef11..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.devicepolicy.flags;
-
-import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
-import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
-
-import android.os.Binder;
-
-public final class FlagUtils {
-    private FlagUtils(){}
-
-    public static boolean isPolicyEngineMigrationV2Enabled() {
-        return Binder.withCleanCallingIdentity(() -> {
-            return policyEngineMigrationV2Enabled();
-        });
-    }
-
-    public static boolean isDevicePolicySizeTrackingEnabled() {
-        return Binder.withCleanCallingIdentity(() -> {
-            return devicePolicySizeTrackingEnabled();
-        });
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
deleted file mode 100644
index 0dde496..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-package: "com.android.server.devicepolicy.flags"
-
-flag {
-  name: "policy_engine_migration_v2_enabled"
-  namespace: "enterprise"
-  description: "V2 of the policy engine migrations for Android V"
-  bug: "289520697"
-}
-flag {
-  name: "device_policy_size_tracking_enabled"
-  namespace: "enterprise"
-  description: "Add feature to track the total policy size and have a max threshold."
-  bug: "281543351"
-}
\ No newline at end of file
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 49ad84a..0d024d6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -51,6 +51,7 @@
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
+import android.graphics.Typeface;
 import android.hardware.display.DisplayManagerInternal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityModuleConnector;
@@ -160,6 +161,7 @@
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
+import com.android.server.pdb.PersistentDataBlockService;
 import com.android.server.people.PeopleService;
 import com.android.server.permission.access.AccessCheckingService;
 import com.android.server.pm.ApexManager;
@@ -336,6 +338,8 @@
             "com.android.clockwork.modes.ModeManagerService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.android.clockwork.display.WearDisplayService";
+    private static final String WEAR_DEBUG_SERVICE_CLASS =
+            "com.android.clockwork.debug.WearDebugService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.android.clockwork.time.WearTimeService";
     private static final String WEAR_SETTINGS_SERVICE_CLASS =
@@ -916,6 +920,14 @@
             SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
             mDumper.addDumpable(tp);
 
+            // Lazily load the pre-installed system font map in SystemServer only if we're not doing
+            // the optimized font loading in the FontManagerService.
+            if (!com.android.text.flags.Flags.useOptimizedBoottimeFontLoading()
+                    && Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
+                Slog.i(TAG, "Loading pre-installed system font map.");
+                Typeface.loadPreinstalledSystemFontMap();
+            }
+
             // Attach JVMTI agent if this is a debuggable build and the system property is set.
             if (Build.IS_DEBUGGABLE) {
                 // Property is of the form "library_path=parameters".
@@ -2071,17 +2083,14 @@
                 t.traceEnd();
             }
 
-            // Devices without WebView/JavaScript cannot support PAC proxies.
-            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
-                t.traceBegin("StartPacProxyService");
-                try {
-                    pacProxyService = new PacProxyService(context);
-                    ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
-                } catch (Throwable e) {
-                    reportWtf("starting PacProxyService", e);
-                }
-                t.traceEnd();
+            t.traceBegin("StartPacProxyService");
+            try {
+                pacProxyService = new PacProxyService(context);
+                ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+            } catch (Throwable e) {
+                reportWtf("starting PacProxyService", e);
             }
+            t.traceEnd();
 
             t.traceBegin("StartConnectivityService");
             // This has to be called after NetworkManagementService, NetworkStatsService
@@ -2626,6 +2635,12 @@
             mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
             t.traceEnd();
 
+            if (Build.IS_DEBUGGABLE) {
+                t.traceBegin("StartWearDebugService");
+                mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS);
+                t.traceEnd();
+            }
+
             t.traceBegin("StartWearTimeService");
             mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
             t.traceEnd();
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 5adcfba..a385fe3 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,5 +18,8 @@
     name: "services.midi",
     defaults: ["platform_service_defaults"],
     srcs: [":services.midi-sources"],
-    libs: ["services.core"],
+    libs: [
+        "services.core",
+        "aconfig_midi_flags_java_lib",
+    ],
 }
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 486ddb4..2f47cc7 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.midi;
 
+import static com.android.media.midi.flags.Flags.virtualUmp;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -1391,7 +1393,6 @@
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
-        Log.d(TAG, "addLegacyPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
@@ -1529,7 +1530,6 @@
 
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
-        Log.d(TAG, "addUmpPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
@@ -1551,6 +1551,12 @@
                 return;
             }
 
+            if (!virtualUmp()) {
+                Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
+                        + ": virtual UMP flag not enabled");
+                return;
+            }
+
             Bundle properties = null;
             int numPorts = 0;
             boolean isPrivate = false;
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index c1d137f..93530cf 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -43,8 +43,7 @@
 
 @Keep
 class AccessCheckingService(context: Context) : SystemService(context) {
-    @Volatile
-    private lateinit var state: AccessState
+    @Volatile private lateinit var state: AccessState
     private val stateLock = Any()
 
     private val policy = AccessPolicy()
@@ -86,17 +85,22 @@
 
         val state = MutableAccessState()
         policy.initialize(
-            state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback,
-            configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist,
+            state,
+            userIds,
+            packageStates,
+            disabledSystemPackageStates,
+            knownPackages,
+            isLeanback,
+            configPermissions,
+            privilegedPermissionAllowlistPackages,
+            permissionAllowlist,
             implicitToSourcePermissions
         )
         persistence.initialize()
         persistence.read(state)
         this.state = state
 
-        mutateState {
-            with(policy) { onInitialized() }
-        }
+        mutateState { with(policy) { onInitialized() } }
 
         appOpService.initialize()
         permissionService.initialize()
@@ -106,40 +110,40 @@
         get() = PackageManager.FEATURE_LEANBACK in availableFeatures
 
     private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String>
-        get() = MutableIndexedListSet<String>().apply {
-            this += "android"
-            if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
-                // Note that SystemProperties.get(String, String) forces returning an empty string
-                // even if we pass null for the def parameter.
-                val carServicePackage = SystemProperties.get("ro.android.car.carservice.package")
-                if (carServicePackage.isNotEmpty()) {
-                    this += carServicePackage
+        get() =
+            MutableIndexedListSet<String>().apply {
+                this += "android"
+                if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
+                    // Note that SystemProperties.get(String, String) forces returning an empty
+                    // string
+                    // even if we pass null for the def parameter.
+                    val carServicePackage =
+                        SystemProperties.get("ro.android.car.carservice.package")
+                    if (carServicePackage.isNotEmpty()) {
+                        this += carServicePackage
+                    }
                 }
             }
-        }
 
     private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
         @Suppress("UNCHECKED_CAST")
-        get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
-            splitPermissions.forEach { splitPermissionInfo ->
-                val sourcePermissionName = splitPermissionInfo.splitPermission
-                splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
-                    getOrPut(implicitPermissionName) { MutableIndexedListSet() } +=
-                        sourcePermissionName
+        get() =
+            MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
+                splitPermissions.forEach { splitPermissionInfo ->
+                    val sourcePermissionName = splitPermissionInfo.splitPermission
+                    splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
+                        getOrPut(implicitPermissionName) { MutableIndexedListSet() } +=
+                            sourcePermissionName
+                    }
                 }
-            }
-        } as IndexedMap<String, IndexedListSet<String>>
+            } as IndexedMap<String, IndexedListSet<String>>
 
     internal fun onUserAdded(userId: Int) {
-        mutateState {
-            with(policy) { onUserAdded(userId) }
-        }
+        mutateState { with(policy) { onUserAdded(userId) } }
     }
 
     internal fun onUserRemoved(userId: Int) {
-        mutateState {
-            with(policy) { onUserRemoved(userId) }
-        }
+        mutateState { with(policy) { onUserRemoved(userId) } }
     }
 
     internal fun onStorageVolumeMounted(
@@ -152,8 +156,12 @@
         mutateState {
             with(policy) {
                 onStorageVolumeMounted(
-                    packageStates, disabledSystemPackageStates, knownPackages, volumeUuid,
-                    packageNames, isSystemUpdated
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    volumeUuid,
+                    packageNames,
+                    isSystemUpdated
                 )
             }
         }
@@ -165,7 +173,10 @@
         mutateState {
             with(policy) {
                 onPackageAdded(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName
                 )
             }
         }
@@ -177,7 +188,11 @@
         mutateState {
             with(policy) {
                 onPackageRemoved(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName, appId
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName,
+                    appId
                 )
             }
         }
@@ -189,7 +204,11 @@
         mutateState {
             with(policy) {
                 onPackageInstalled(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName, userId
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName,
+                    userId
                 )
             }
         }
@@ -201,7 +220,11 @@
         mutateState {
             with(policy) {
                 onPackageUninstalled(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName, appId,
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName,
+                    appId,
                     userId
                 )
             }
@@ -224,34 +247,42 @@
 
     private fun PackageManagerInternal.getKnownPackages(
         packageStates: Map<String, PackageState>
-    ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply {
-        this[KnownPackages.PACKAGE_INSTALLER] =
-            getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames(
-            KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
-        )
-        this[KnownPackages.PACKAGE_VERIFIER] =
-            getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_SETUP_WIZARD] =
-            getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames(
-            KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM
-        )
-        this[KnownPackages.PACKAGE_CONFIGURATOR] =
-            getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames(
-            KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM
-        )
-        this[KnownPackages.PACKAGE_APP_PREDICTOR] =
-            getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_COMPANION] =
-            getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_RETAIL_DEMO] =
-            getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM)
-                .filter { isProfileOwner(it, packageStates) }.toTypedArray()
-        this[KnownPackages.PACKAGE_RECENTS] =
-            getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM)
-    }
+    ): IntMap<Array<String>> =
+        MutableIntMap<Array<String>>().apply {
+            this[KnownPackages.PACKAGE_INSTALLER] =
+                getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] =
+                getKnownPackageNames(
+                    KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
+                    UserHandle.USER_SYSTEM
+                )
+            this[KnownPackages.PACKAGE_VERIFIER] =
+                getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_SETUP_WIZARD] =
+                getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] =
+                getKnownPackageNames(
+                    KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+                    UserHandle.USER_SYSTEM
+                )
+            this[KnownPackages.PACKAGE_CONFIGURATOR] =
+                getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] =
+                getKnownPackageNames(
+                    KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER,
+                    UserHandle.USER_SYSTEM
+                )
+            this[KnownPackages.PACKAGE_APP_PREDICTOR] =
+                getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_COMPANION] =
+                getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_RETAIL_DEMO] =
+                getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM)
+                    .filter { isProfileOwner(it, packageStates) }
+                    .toTypedArray()
+            this[KnownPackages.PACKAGE_RECENTS] =
+                getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM)
+        }
 
     private fun isProfileOwner(
         packageName: String,
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index a3f65af..d0913d2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -38,16 +38,11 @@
 import java.io.File
 import java.io.FileNotFoundException
 
-class AccessPersistence(
-    private val policy: AccessPolicy
-) {
+class AccessPersistence(private val policy: AccessPolicy) {
     private val scheduleLock = Any()
-    @GuardedBy("scheduleLock")
-    private val pendingMutationTimesMillis = SparseLongArray()
-    @GuardedBy("scheduleLock")
-    private val pendingStates = MutableIntMap<AccessState>()
-    @GuardedBy("scheduleLock")
-    private lateinit var writeHandler: WriteHandler
+    @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray()
+    @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>()
+    @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler
 
     private val writeLock = Any()
 
@@ -60,17 +55,16 @@
      */
     fun read(state: MutableAccessState) {
         readSystemState(state)
-        state.externalState.userIds.forEachIndexed { _, userId ->
-            readUserState(state, userId)
-        }
+        state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) }
     }
 
     private fun readSystemState(state: MutableAccessState) {
-        val fileExists = systemFile.parse {
-            // This is the canonical way to call an extension function in a different class.
-            // TODO(b/259469752): Use context receiver for this when it becomes stable.
-            with(policy) { parseSystemState(state) }
-        }
+        val fileExists =
+            systemFile.parse {
+                // This is the canonical way to call an extension function in a different class.
+                // TODO(b/259469752): Use context receiver for this when it becomes stable.
+                with(policy) { parseSystemState(state) }
+            }
 
         if (!fileExists) {
             policy.migrateSystemState(state)
@@ -79,9 +73,8 @@
     }
 
     private fun readUserState(state: MutableAccessState, userId: Int) {
-        val fileExists = getUserFile(userId).parse {
-            with(policy) { parseUserState(state, userId) }
-        }
+        val fileExists =
+            getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } }
 
         if (!fileExists) {
             policy.migrateUserState(state, userId)
@@ -90,8 +83,8 @@
     }
 
     /**
-     * @return {@code true} if the file is successfully read from the disk; {@code false} if
-     * the file doesn't exist yet.
+     * @return {@code true} if the file is successfully read from the disk; {@code false} if the
+     *   file doesn't exist yet.
      */
     private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean =
         try {
@@ -106,9 +99,7 @@
 
     fun write(state: AccessState) {
         state.systemState.write(state, UserHandle.USER_ALL)
-        state.userStates.forEachIndexed { _, userId, userState ->
-            userState.write(state, userId)
-        }
+        state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) }
     }
 
     private fun WritableState.write(state: AccessState, userId: Int) {
@@ -127,8 +118,10 @@
                     if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) {
                         message.sendToTarget()
                     } else {
-                        val newDelayMillis = WRITE_DELAY_TIME_MILLIS
-                            .coerceAtMost(MAX_WRITE_DELAY_MILLIS - currentDelayMillis)
+                        val newDelayMillis =
+                            WRITE_DELAY_TIME_MILLIS.coerceAtMost(
+                                MAX_WRITE_DELAY_MILLIS - currentDelayMillis
+                            )
                         writeHandler.sendMessageDelayed(message, newDelayMillis)
                     }
                 }
@@ -161,15 +154,11 @@
     }
 
     private fun writeSystemState(state: AccessState) {
-        systemFile.serialize {
-            with(policy) { serializeSystemState(state) }
-        }
+        systemFile.serialize { with(policy) { serializeSystemState(state) } }
     }
 
     private fun writeUserState(state: AccessState, userId: Int) {
-        getUserFile(userId).serialize {
-            with(policy) { serializeUserState(state, userId) }
-        }
+        getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } }
     }
 
     private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) {
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 6a349e2..754f77ec 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -37,21 +37,24 @@
 import com.android.server.pm.permission.PermissionAllowlist
 import com.android.server.pm.pkg.PackageState
 
-class AccessPolicy private constructor(
+class AccessPolicy
+private constructor(
     private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
 ) {
     @Suppress("UNCHECKED_CAST")
-    constructor() : this(
-        MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply {
-            fun addPolicy(policy: SchemePolicy) {
-                getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
-            }
-            addPolicy(AppIdPermissionPolicy())
-            addPolicy(DevicePermissionPolicy())
-            addPolicy(AppIdAppOpPolicy())
-            addPolicy(PackageAppOpPolicy())
-        } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
-    )
+    constructor() :
+        this(
+            MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply {
+                fun addPolicy(policy: SchemePolicy) {
+                    getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] =
+                        policy
+                }
+                addPolicy(AppIdPermissionPolicy())
+                addPolicy(DevicePermissionPolicy())
+                addPolicy(AppIdAppOpPolicy())
+                addPolicy(PackageAppOpPolicy())
+            } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
+        )
 
     fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
         checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) {
@@ -92,23 +95,17 @@
     }
 
     fun GetStateScope.onStateMutated() {
-        forEachSchemePolicy {
-            with(it) { onStateMutated() }
-        }
+        forEachSchemePolicy { with(it) { onStateMutated() } }
     }
 
     fun MutateStateScope.onInitialized() {
-        forEachSchemePolicy {
-            with(it) { onInitialized() }
-        }
+        forEachSchemePolicy { with(it) { onInitialized() } }
     }
 
     fun MutateStateScope.onUserAdded(userId: Int) {
         newState.mutateExternalState().mutateUserIds() += userId
         newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
-        forEachSchemePolicy {
-            with(it) { onUserAdded(userId) }
-        }
+        forEachSchemePolicy { with(it) { onUserAdded(userId) } }
         newState.externalState.packageStates.forEach { (_, packageState) ->
             upgradePackageVersion(packageState, userId)
         }
@@ -117,9 +114,7 @@
     fun MutateStateScope.onUserRemoved(userId: Int) {
         newState.mutateExternalState().mutateUserIds() -= userId
         newState.mutateUserStatesNoWrite() -= userId
-        forEachSchemePolicy {
-            with(it) { onUserRemoved(userId) }
-        }
+        forEachSchemePolicy { with(it) { onUserRemoved(userId) } }
     }
 
     fun MutateStateScope.onStorageVolumeMounted(
@@ -154,9 +149,7 @@
             setKnownPackages(knownPackages)
         }
         addedAppIds.forEachIndexed { _, appId ->
-            forEachSchemePolicy {
-                with(it) { onAppIdAdded(appId) }
-            }
+            forEachSchemePolicy { with(it) { onAppIdAdded(appId) } }
         }
         forEachSchemePolicy {
             with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
@@ -192,13 +185,9 @@
             setKnownPackages(knownPackages)
         }
         if (isAppIdAdded) {
-            forEachSchemePolicy {
-                with(it) { onAppIdAdded(appId) }
-            }
+            forEachSchemePolicy { with(it) { onAppIdAdded(appId) } }
         }
-        forEachSchemePolicy {
-            with(it) { onPackageAdded(packageState) }
-        }
+        forEachSchemePolicy { with(it) { onPackageAdded(packageState) } }
         newState.userStates.forEachIndexed { _, userId, _ ->
             upgradePackageVersion(packageState, userId)
         }
@@ -227,13 +216,9 @@
             }
             setKnownPackages(knownPackages)
         }
-        forEachSchemePolicy {
-            with(it) { onPackageRemoved(packageName, appId) }
-        }
+        forEachSchemePolicy { with(it) { onPackageRemoved(packageName, appId) } }
         if (isAppIdRemoved) {
-            forEachSchemePolicy {
-                with(it) { onAppIdRemoved(appId) }
-            }
+            forEachSchemePolicy { with(it) { onAppIdRemoved(appId) } }
         }
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             if (packageName in userState.packageVersions) {
@@ -258,9 +243,7 @@
         checkNotNull(packageState) {
             "Installed package $packageName isn't found in packageStates in onPackageInstalled()"
         }
-        forEachSchemePolicy {
-            with(it) { onPackageInstalled(packageState, userId) }
-        }
+        forEachSchemePolicy { with(it) { onPackageInstalled(packageState, userId) } }
     }
 
     fun MutateStateScope.onPackageUninstalled(
@@ -276,9 +259,7 @@
             setDisabledSystemPackageStates(disabledSystemPackageStates)
             setKnownPackages(knownPackages)
         }
-        forEachSchemePolicy {
-            with(it) { onPackageUninstalled(packageName, appId, userId) }
-        }
+        forEachSchemePolicy { with(it) { onPackageUninstalled(packageName, appId, userId) } }
     }
 
     fun MutateStateScope.onSystemReady(
@@ -292,21 +273,15 @@
             setKnownPackages(knownPackages)
             setSystemReady(true)
         }
-        forEachSchemePolicy {
-            with(it) { onSystemReady() }
-        }
+        forEachSchemePolicy { with(it) { onSystemReady() } }
     }
 
     fun migrateSystemState(state: MutableAccessState) {
-        forEachSchemePolicy {
-            with(it) { migrateSystemState(state) }
-        }
+        forEachSchemePolicy { with(it) { migrateSystemState(state) } }
     }
 
     fun migrateUserState(state: MutableAccessState, userId: Int) {
-        forEachSchemePolicy {
-            with(it) { migrateUserState(state, userId) }
-        }
+        forEachSchemePolicy { with(it) { migrateUserState(state, userId) } }
     }
 
     private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) {
@@ -330,10 +305,12 @@
                     VERSION_LATEST
             }
             version == VERSION_LATEST -> {}
-            else -> Slog.w(
-                LOG_TAG, "Unexpected version $version for package $packageName," +
-                    "latest version is $VERSION_LATEST"
-            )
+            else ->
+                Slog.w(
+                    LOG_TAG,
+                    "Unexpected version $version for package $packageName," +
+                        "latest version is $VERSION_LATEST"
+                )
         }
     }
 
@@ -341,11 +318,7 @@
         forEachTag {
             when (tagName) {
                 TAG_ACCESS -> {
-                    forEachTag {
-                        forEachSchemePolicy {
-                            with(it) { parseSystemState(state) }
-                        }
-                    }
+                    forEachTag { forEachSchemePolicy { with(it) { parseSystemState(state) } } }
                 }
                 else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
             }
@@ -353,11 +326,7 @@
     }
 
     fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
-        tag(TAG_ACCESS) {
-            forEachSchemePolicy {
-                with(it) { serializeSystemState(state) }
-            }
-        }
+        tag(TAG_ACCESS) { forEachSchemePolicy { with(it) { serializeSystemState(state) } } }
     }
 
     fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
@@ -370,9 +339,7 @@
                             TAG_DEFAULT_PERMISSION_GRANT ->
                                 parseDefaultPermissionGrant(state, userId)
                             else -> {
-                                forEachSchemePolicy {
-                                    with(it) { parseUserState(state, userId) }
-                                }
+                                forEachSchemePolicy { with(it) { parseUserState(state, userId) } }
                             }
                         }
                     }
@@ -428,9 +395,7 @@
             serializeDefaultPermissionGrantFingerprint(
                 state.userStates[userId]!!.defaultPermissionGrantFingerprint
             )
-            forEachSchemePolicy {
-                with(it) { serializeUserState(state, userId) }
-            }
+            forEachSchemePolicy { with(it) { serializeUserState(state, userId) } }
         }
     }
 
@@ -451,9 +416,7 @@
         fingerprint: String?
     ) {
         if (fingerprint != null) {
-            tag(TAG_DEFAULT_PERMISSION_GRANT) {
-                attributeInterned(ATTR_FINGERPRINT, fingerprint)
-            }
+            tag(TAG_DEFAULT_PERMISSION_GRANT) { attributeInterned(ATTR_FINGERPRINT, fingerprint) }
         }
     }
 
@@ -462,9 +425,7 @@
 
     private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
         schemePolicies.forEachIndexed { _, _, objectSchemePolicies ->
-            objectSchemePolicies.forEachIndexed { _, _, schemePolicy ->
-                action(schemePolicy)
-            }
+            objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> action(schemePolicy) }
         }
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 94c878a..49d2f81 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -28,7 +28,9 @@
 private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState>
 
 typealias UserStates = IntReferenceMap<UserState, MutableUserState>
+
 typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState>
+
 private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates>
 
 sealed class AccessState(
@@ -48,22 +50,22 @@
     override fun toMutable(): MutableAccessState = MutableAccessState(this)
 }
 
-class MutableAccessState private constructor(
+class MutableAccessState
+private constructor(
     externalStateReference: ExternalStateReference,
     systemStateReference: SystemStateReference,
     userStatesReference: UserStatesReference
-) : AccessState(
-    externalStateReference,
-    systemStateReference,
-    userStatesReference
-) {
-    constructor() : this(
-        ExternalStateReference(MutableExternalState()),
-        SystemStateReference(MutableSystemState()),
-        UserStatesReference(MutableUserStates())
-    )
+) : AccessState(externalStateReference, systemStateReference, userStatesReference) {
+    constructor() :
+        this(
+            ExternalStateReference(MutableExternalState()),
+            SystemStateReference(MutableSystemState()),
+            UserStatesReference(MutableUserStates())
+        )
 
-    internal constructor(accessState: AccessState) : this(
+    internal constructor(
+        accessState: AccessState
+    ) : this(
         accessState.externalStateReference.toImmutable(),
         accessState.systemStateReference.toImmutable(),
         accessState.userStatesReference.toImmutable()
@@ -86,8 +88,10 @@
 private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet>
 
 typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+
 typealias MutableAppIdPackageNames =
     MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+
 private typealias AppIdPackageNamesReference =
     MutableReference<AppIdPackageNames, MutableAppIdPackageNames>
 
@@ -142,7 +146,8 @@
     override fun toMutable(): MutableExternalState = MutableExternalState(this)
 }
 
-class MutableExternalState private constructor(
+class MutableExternalState
+private constructor(
     userIdsReference: UserIdsReference,
     packageStates: Map<String, PackageState>,
     disabledSystemPackageStates: Map<String, PackageState>,
@@ -154,34 +159,38 @@
     permissionAllowlist: PermissionAllowlist,
     implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
     isSystemReady: Boolean
-) : ExternalState(
-    userIdsReference,
-    packageStates,
-    disabledSystemPackageStates,
-    appIdPackageNamesReference,
-    knownPackages,
-    isLeanback,
-    configPermissions,
-    privilegedPermissionAllowlistPackages,
-    permissionAllowlist,
-    implicitToSourcePermissions,
-    isSystemReady
-) {
-    constructor() : this(
-        UserIdsReference(MutableIntSet()),
-        emptyMap(),
-        emptyMap(),
-        AppIdPackageNamesReference(MutableAppIdPackageNames()),
-        MutableIntMap(),
-        false,
-        emptyMap(),
-        MutableIndexedListSet(),
-        PermissionAllowlist(),
-        MutableIndexedMap(),
-        false
-    )
+) :
+    ExternalState(
+        userIdsReference,
+        packageStates,
+        disabledSystemPackageStates,
+        appIdPackageNamesReference,
+        knownPackages,
+        isLeanback,
+        configPermissions,
+        privilegedPermissionAllowlistPackages,
+        permissionAllowlist,
+        implicitToSourcePermissions,
+        isSystemReady
+    ) {
+    constructor() :
+        this(
+            UserIdsReference(MutableIntSet()),
+            emptyMap(),
+            emptyMap(),
+            AppIdPackageNamesReference(MutableAppIdPackageNames()),
+            MutableIntMap(),
+            false,
+            emptyMap(),
+            MutableIndexedListSet(),
+            PermissionAllowlist(),
+            MutableIndexedMap(),
+            false
+        )
 
-    internal constructor(externalState: ExternalState) : this(
+    internal constructor(
+        externalState: ExternalState
+    ) : this(
         externalState.userIdsReference.toImmutable(),
         externalState.packageStates,
         externalState.disabledSystemPackageStates,
@@ -249,9 +258,10 @@
     }
 }
 
-private typealias PermissionGroupsReference = MutableReference<
-    IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo>
->
+private typealias PermissionGroupsReference =
+    MutableReference<
+        IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo>
+    >
 
 private typealias PermissionTreesReference =
     MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
@@ -280,25 +290,31 @@
     override fun toMutable(): MutableSystemState = MutableSystemState(this)
 }
 
-class MutableSystemState private constructor(
+class MutableSystemState
+private constructor(
     permissionGroupsReference: PermissionGroupsReference,
     permissionTreesReference: PermissionTreesReference,
     permissionsReference: PermissionsReference,
     writeMode: Int
-) : SystemState(
-    permissionGroupsReference,
-    permissionTreesReference,
-    permissionsReference,
-    writeMode
-), MutableWritableState {
-    constructor() : this(
-        PermissionGroupsReference(MutableIndexedMap()),
-        PermissionTreesReference(MutableIndexedMap()),
-        PermissionsReference(MutableIndexedMap()),
-        WriteMode.NONE
-    )
+) :
+    SystemState(
+        permissionGroupsReference,
+        permissionTreesReference,
+        permissionsReference,
+        writeMode
+    ),
+    MutableWritableState {
+    constructor() :
+        this(
+            PermissionGroupsReference(MutableIndexedMap()),
+            PermissionTreesReference(MutableIndexedMap()),
+            PermissionsReference(MutableIndexedMap()),
+            WriteMode.NONE
+        )
 
-    internal constructor(systemState: SystemState) : this(
+    internal constructor(
+        systemState: SystemState
+    ) : this(
         systemState.permissionGroupsReference.toImmutable(),
         systemState.permissionTreesReference.toImmutable(),
         systemState.permissionsReference.toImmutable(),
@@ -311,8 +327,7 @@
     fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> =
         permissionTreesReference.mutate()
 
-    fun mutatePermissions(): MutableIndexedMap<String, Permission> =
-        permissionsReference.mutate()
+    fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate()
 
     override fun requestWriteMode(writeMode: Int) {
         this.writeMode = maxOf(this.writeMode, writeMode)
@@ -324,34 +339,42 @@
 
 typealias AppIdPermissionFlags =
     IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutableAppIdPermissionFlags =
     MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 private typealias AppIdPermissionFlagsReference =
     MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
 
-
 typealias DevicePermissionFlags =
     IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutableDevicePermissionFlags =
     MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias AppIdDevicePermissionFlags =
     IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+
 typealias MutableAppIdDevicePermissionFlags =
     MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+
 private typealias AppIdDevicePermissionFlagsReference =
     MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags>
 
-typealias AppIdAppOpModes =
-    IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutableAppIdAppOpModes =
     MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 private typealias AppIdAppOpModesReference =
     MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes>
 
 typealias PackageAppOpModes =
     IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutablePackageAppOpModes =
     MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 private typealias PackageAppOpModesReference =
     MutableReference<PackageAppOpModes, MutablePackageAppOpModes>
 
@@ -388,7 +411,8 @@
     override fun toMutable(): MutableUserState = MutableUserState(this)
 }
 
-class MutableUserState private constructor(
+class MutableUserState
+private constructor(
     packageVersionsReference: PackageVersionsReference,
     appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
     appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
@@ -396,26 +420,31 @@
     packageAppOpModesReference: PackageAppOpModesReference,
     defaultPermissionGrantFingerprint: String?,
     writeMode: Int
-) : UserState(
-    packageVersionsReference,
-    appIdPermissionFlagsReference,
-    appIdDevicePermissionFlagsReference,
-    appIdAppOpModesReference,
-    packageAppOpModesReference,
-    defaultPermissionGrantFingerprint,
-    writeMode
-), MutableWritableState {
-    constructor() : this(
-        PackageVersionsReference(MutableIndexedMap<String, Int>()),
-        AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
-        AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
-        AppIdAppOpModesReference(MutableAppIdAppOpModes()),
-        PackageAppOpModesReference(MutablePackageAppOpModes()),
-        null,
-        WriteMode.NONE
-    )
+) :
+    UserState(
+        packageVersionsReference,
+        appIdPermissionFlagsReference,
+        appIdDevicePermissionFlagsReference,
+        appIdAppOpModesReference,
+        packageAppOpModesReference,
+        defaultPermissionGrantFingerprint,
+        writeMode
+    ),
+    MutableWritableState {
+    constructor() :
+        this(
+            PackageVersionsReference(MutableIndexedMap<String, Int>()),
+            AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
+            AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
+            AppIdAppOpModesReference(MutableAppIdAppOpModes()),
+            PackageAppOpModesReference(MutablePackageAppOpModes()),
+            null,
+            WriteMode.NONE
+        )
 
-    internal constructor(userState: UserState) : this(
+    internal constructor(
+        userState: UserState
+    ) : this(
         userState.packageVersionsReference.toImmutable(),
         userState.appIdPermissionFlagsReference.toImmutable(),
         userState.appIdDevicePermissionFlagsReference.toImmutable(),
@@ -461,11 +490,7 @@
     fun requestWriteMode(writeMode: Int)
 }
 
-open class GetStateScope(
-    val state: AccessState
-)
+open class GetStateScope(val state: AccessState)
 
-class MutateStateScope(
-    val oldState: AccessState,
-    val newState: MutableAccessState
-) : GetStateScope(newState)
+class MutateStateScope(val oldState: AccessState, val newState: MutableAccessState) :
+    GetStateScope(newState)
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
index 1d46ca7..1f5a4df 100644
--- a/services/permission/java/com/android/server/permission/access/AccessUri.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -18,9 +18,7 @@
 
 import android.os.UserHandle
 
-sealed class AccessUri(
-    val scheme: String
-) {
+sealed class AccessUri(val scheme: String) {
     override fun equals(other: Any?): Boolean {
         throw NotImplementedError()
     }
@@ -34,9 +32,7 @@
     }
 }
 
-data class AppOpUri(
-    val appOpName: String
-) : AccessUri(SCHEME) {
+data class AppOpUri(val appOpName: String) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$appOpName"
 
     companion object {
@@ -44,10 +40,7 @@
     }
 }
 
-data class PackageUri(
-    val packageName: String,
-    val userId: Int
-) : AccessUri(SCHEME) {
+data class PackageUri(val packageName: String, val userId: Int) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$packageName/$userId"
 
     companion object {
@@ -55,9 +48,7 @@
     }
 }
 
-data class PermissionUri(
-    val permissionName: String
-) : AccessUri(SCHEME) {
+data class PermissionUri(val permissionName: String) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$permissionName"
 
     companion object {
@@ -65,10 +56,7 @@
     }
 }
 
-data class DevicePermissionUri(
-    val permissionName: String,
-    val deviceId: Int
-) : AccessUri(SCHEME) {
+data class DevicePermissionUri(val permissionName: String, val deviceId: Int) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$permissionName/$deviceId"
 
     companion object {
@@ -76,9 +64,7 @@
     }
 }
 
-data class UidUri(
-    val uid: Int
-) : AccessUri(SCHEME) {
+data class UidUri(val uid: Int) : AccessUri(SCHEME) {
     val userId: Int
         get() = UserHandle.getUserId(uid)
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
index 96d315e..6c1b080 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
@@ -46,9 +46,7 @@
 
             val appOpModes = MutableIndexedMap<String, Int>()
             appIdAppOpModes[appId] = appOpModes
-            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
-                appOpModes[appOpName] = appOpMode
-            }
+            legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode }
 
             if (packageNames != null) {
                 val packageVersions = userState.mutatePackageVersions()
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
index 4c7e946..f291b1a 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
@@ -51,8 +51,10 @@
         }
         userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ ->
             // Non-application UIDs may not have an Android package but may still have app op state.
-            if (appId !in state.externalState.appIdPackageNames &&
-                appId >= Process.FIRST_APPLICATION_UID) {
+            if (
+                appId !in state.externalState.appIdPackageNames &&
+                    appId >= Process.FIRST_APPLICATION_UID
+            ) {
                 Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
                 appIdAppOpModes.removeAt(appIdIndex)
                 userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
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 c02fe4d..94caf28 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
@@ -46,7 +46,9 @@
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId)
             if (appIdIndex >= 0) {
-                newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes()
+                newState
+                    .mutateUserStateAt(userStateIndex)
+                    .mutateAppIdAppOpModes()
                     .removeAt(appIdIndex)
                 // Skip notifying the change listeners since the app ID no longer exists.
             }
@@ -61,8 +63,8 @@
         if (userStateIndex < 0) {
             return false
         }
-        val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes
-            .indexOfKey(appId)
+        val appIdIndex =
+            newState.userStates.valueAt(userStateIndex).appIdAppOpModes.indexOfKey(appId)
         if (appIdIndex < 0) {
             return false
         }
@@ -71,7 +73,9 @@
     }
 
     fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
-        state.userStates[userId]?.appIdAppOpModes?.get(appId)
+        state.userStates[userId]
+            ?.appIdAppOpModes
+            ?.get(appId)
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -81,8 +85,10 @@
         mode: Int
     ): Boolean {
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId]
-            .getWithDefault(appOpName, defaultMode)
+        val oldMode =
+            newState.userStates[userId]!!
+                .appIdAppOpModes[appId]
+                .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
@@ -122,9 +128,7 @@
         with(upgrade) { upgradePackageState(packageState, userId, version) }
     }
 
-    /**
-     * Listener for app op mode changes.
-     */
+    /** Listener for app op mode changes. */
     abstract class OnAppOpModeChangedListener {
         /**
          * Called when an app op mode change has been made to the upcoming new state.
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
index 12df95e..10c7764 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
@@ -28,11 +28,13 @@
     ) {
         if (version <= 2) {
             with(policy) {
-                val appOpMode = getAppOpMode(
-                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
-                )
+                val appOpMode =
+                    getAppOpMode(packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND)
                 setAppOpMode(
-                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode
+                    packageState.appId,
+                    userId,
+                    AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+                    appOpMode
                 )
             }
         }
@@ -40,14 +42,19 @@
             val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
             if (permissionName in packageState.androidPackage!!.requestedPermissions) {
                 with(policy) {
-                    val appOpMode = getAppOpMode(
-                        packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
-                    )
+                    val appOpMode =
+                        getAppOpMode(
+                            packageState.appId,
+                            userId,
+                            AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
+                        )
                     val defaultAppOpMode =
                         AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
                     if (appOpMode == defaultAppOpMode) {
                         setAppOpMode(
-                            packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                            packageState.appId,
+                            userId,
+                            AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
                             AppOpsManager.MODE_ALLOWED
                         )
                     }
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 5b91ad9..26ea9d2 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -33,19 +33,16 @@
 import com.android.server.permission.access.collection.forEachIndexed
 import com.android.server.permission.access.collection.set
 
-class AppOpService(
-    private val service: AccessCheckingService
-) : AppOpsCheckingServiceInterface {
-    private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME)
-        as PackageAppOpPolicy
-    private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
-        as AppIdAppOpPolicy
+class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
+    private val packagePolicy =
+        service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
+    private val appIdPolicy =
+        service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
 
     private val context = service.context
     private lateinit var handler: Handler
 
-    @Volatile
-    private var listeners = ArraySet<AppOpsModeChangedListener>()
+    @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
     private val listenersLock = Any()
 
     fun initialize() {
@@ -86,9 +83,7 @@
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
-        return service.getState {
-            with(appIdPolicy) { getAppOpMode(appId, userId, opName) }
-        }
+        return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
     }
 
     private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -115,10 +110,7 @@
         }
     }
 
-    private fun getPackageModes(
-        packageName: String,
-        userId: Int
-    ): ArrayMap<String, Int>? =
+    private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
         service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
 
     override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
@@ -131,15 +123,13 @@
     override fun removeUid(uid: Int) {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
-        service.mutateState {
-            with(appIdPolicy) { removeAppOpModes(appId, userId) }
-        }
+        service.mutateState { with(appIdPolicy) { removeAppOpModes(appId, userId) } }
     }
 
     override fun removePackage(packageName: String, userId: Int): Boolean {
         var wasChanged = false
         service.mutateState {
-            wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) }
+            wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
         }
         return wasChanged
     }
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
index a267637..edeef71 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
@@ -52,9 +52,7 @@
     }
 
     protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) {
-        appOpModes.forEachIndexed { _, name, mode ->
-            serializeAppOp(name, mode)
-        }
+        appOpModes.forEachIndexed { _, name, mode -> serializeAppOp(name, mode) }
     }
 
     private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) {
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index c0a85f8..758cec0 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -23,9 +23,7 @@
 import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.SchemePolicy
 
-abstract class BaseAppOpPolicy(
-    private val persistence: BaseAppOpPersistence
-) : SchemePolicy() {
+abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
     override val objectScheme: String
         get() = AppOpUri.SCHEME
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
index 03311a2..8797e39 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
@@ -44,9 +44,7 @@
 
             val appOpModes = MutableIndexedMap<String, Int>()
             packageAppOpModes[packageName] = appOpModes
-            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
-                appOpModes[appOpName] = appOpMode
-            }
+            legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode }
 
             userState.mutatePackageVersions()[packageName] = version
         }
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 5398a57..0d9470e 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
@@ -46,7 +46,9 @@
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName)
             if (packageNameIndex >= 0) {
-                newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+                newState
+                    .mutateUserStateAt(userStateIndex)
+                    .mutatePackageAppOpModes()
                     .removeAt(packageNameIndex)
                 // Skip notifying the change listeners since the package no longer exists.
             }
@@ -61,18 +63,22 @@
         if (userStateIndex < 0) {
             return false
         }
-        val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes
-            .indexOfKey(packageName)
+        val packageNameIndex =
+            newState.userStates.valueAt(userStateIndex).packageAppOpModes.indexOfKey(packageName)
         if (packageNameIndex < 0) {
             return false
         }
-        newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+        newState
+            .mutateUserStateAt(userStateIndex)
+            .mutatePackageAppOpModes()
             .removeAt(packageNameIndex)
         return true
     }
 
     fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
-        state.userStates[userId]?.packageAppOpModes?.get(packageName)
+        state.userStates[userId]
+            ?.packageAppOpModes
+            ?.get(packageName)
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -82,8 +88,10 @@
         mode: Int
     ): Boolean {
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName]
-            .getWithDefault(appOpName, defaultMode)
+        val oldMode =
+            newState.userStates[userId]!!
+                .packageAppOpModes[packageName]
+                .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
@@ -123,9 +131,7 @@
         with(upgrade) { upgradePackageState(packageState, userId, version) }
     }
 
-    /**
-     * Listener for app op mode changes.
-     */
+    /** Listener for app op mode changes. */
     abstract class OnAppOpModeChangedListener {
         /**
          * Called when an app op mode change has been made to the upcoming new state.
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
index 8e37093..f5eedf7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
@@ -28,11 +28,16 @@
     ) {
         if (version <= 2) {
             with(policy) {
-                val appOpMode = getAppOpMode(
-                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
-                )
+                val appOpMode =
+                    getAppOpMode(
+                        packageState.packageName,
+                        userId,
+                        AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+                    )
                 setAppOpMode(
-                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+                    packageState.packageName,
+                    userId,
+                    AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
                     appOpMode
                 )
             }
diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
index 686db42..b74f477 100644
--- a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
@@ -49,7 +49,9 @@
 }
 
 inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
-    get(key)?.let { return it }
+    get(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
index ce4aa44..ea8e07f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
@@ -16,12 +16,8 @@
 
 package com.android.server.permission.access.immutable
 
-/**
- * Immutable list with index-based access.
- */
-sealed class IndexedList<T>(
-    internal val list: ArrayList<T>
-) : Immutable<MutableIndexedList<T>> {
+/** Immutable list with index-based access. */
+sealed class IndexedList<T>(internal val list: ArrayList<T>) : Immutable<MutableIndexedList<T>> {
     val size: Int
         get() = list.size
 
@@ -29,20 +25,15 @@
 
     operator fun contains(element: T): Boolean = list.contains(element)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(index: Int): T = list.get(index)
+    @Suppress("ReplaceGetOrSet") operator fun get(index: Int): T = list.get(index)
 
     override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this)
 
     override fun toString(): String = list.toString()
 }
 
-/**
- * Mutable list with index-based access.
- */
-class MutableIndexedList<T>(
-    list: ArrayList<T> = ArrayList()
-) : IndexedList<T>(list) {
+/** Mutable list with index-based access. */
+class MutableIndexedList<T>(list: ArrayList<T> = ArrayList()) : IndexedList<T>(list) {
     constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list))
 
     @Suppress("ReplaceGetOrSet")
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
index dc9bae3..a9d804e 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
@@ -70,9 +70,7 @@
     accumulator: (Int, Int, T) -> Int
 ): Int {
     var value = initialValue
-    forEachIndexed { index, element ->
-        value = accumulator(value, index, element)
-    }
+    forEachIndexed { index, element -> value = accumulator(value, index, element) }
     return value
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
index 77e71ba..3a2fd2f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
@@ -16,12 +16,9 @@
 
 package com.android.server.permission.access.immutable
 
-/**
- * Immutable set with index-based access, implemented using a list.
- */
-sealed class IndexedListSet<T>(
-    internal val list: ArrayList<T>
-) : Immutable<MutableIndexedListSet<T>> {
+/** Immutable set with index-based access, implemented using a list. */
+sealed class IndexedListSet<T>(internal val list: ArrayList<T>) :
+    Immutable<MutableIndexedListSet<T>> {
     val size: Int
         get() = list.size
 
@@ -31,20 +28,15 @@
 
     fun indexOf(element: T): Int = list.indexOf(element)
 
-    @Suppress("ReplaceGetOrSet")
-    fun elementAt(index: Int): T = list.get(index)
+    @Suppress("ReplaceGetOrSet") fun elementAt(index: Int): T = list.get(index)
 
     override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this)
 
     override fun toString(): String = list.toString()
 }
 
-/**
- * Mutable set with index-based access, implemented using a list.
- */
-class MutableIndexedListSet<T>(
-    list: ArrayList<T> = ArrayList()
-) : IndexedListSet<T>(list) {
+/** Mutable set with index-based access, implemented using a list. */
+class MutableIndexedListSet<T>(list: ArrayList<T> = ArrayList()) : IndexedListSet<T>(list) {
     constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list))
 
     fun add(element: T): Boolean =
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
index 13fc141..2634b53 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
@@ -70,9 +70,7 @@
     accumulator: (Int, Int, T) -> Int
 ): Int {
     var value = initialValue
-    forEachIndexed { index, element ->
-        value = accumulator(value, index, element)
-    }
+    forEachIndexed { index, element -> value = accumulator(value, index, element) }
     return value
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
index 299cc89..873c9c8 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
@@ -18,12 +18,9 @@
 
 import android.util.ArrayMap
 
-/**
- * Immutable map with index-based access.
- */
-sealed class IndexedMap<K, V>(
-    internal val map: ArrayMap<K, V>
-) : Immutable<MutableIndexedMap<K, V>> {
+/** Immutable map with index-based access. */
+sealed class IndexedMap<K, V>(internal val map: ArrayMap<K, V>) :
+    Immutable<MutableIndexedMap<K, V>> {
     val size: Int
         get() = map.size
 
@@ -31,8 +28,7 @@
 
     operator fun contains(key: K): Boolean = map.containsKey(key)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(key: K): V? = map.get(key)
+    @Suppress("ReplaceGetOrSet") operator fun get(key: K): V? = map.get(key)
 
     fun indexOfKey(key: K): Int = map.indexOfKey(key)
 
@@ -45,12 +41,8 @@
     override fun toString(): String = map.toString()
 }
 
-/**
- * Mutable map with index-based access.
- */
-class MutableIndexedMap<K, V>(
-    map: ArrayMap<K, V> = ArrayMap()
-) : IndexedMap<K, V>(map) {
+/** Mutable map with index-based access. */
+class MutableIndexedMap<K, V>(map: ArrayMap<K, V> = ArrayMap()) : IndexedMap<K, V>(map) {
     constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map))
 
     fun put(key: K, value: V): V? = map.put(key, value)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
index 69f1779c..48637cc 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
@@ -36,7 +36,9 @@
 
 inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
     forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
+        transform(index, key, value)?.let {
+            return it
+        }
     }
     return null
 }
@@ -75,9 +77,7 @@
     destination: C,
     transform: (Int, K, V) -> R,
 ): C {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value).let { destination += it }
-    }
+    forEachIndexed { index, key, value -> transform(index, key, value).let { destination += it } }
     return destination
 }
 
@@ -85,14 +85,14 @@
     destination: C,
     transform: (Int, K, V) -> R?
 ): C {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { destination += it }
-    }
+    forEachIndexed { index, key, value -> transform(index, key, value)?.let { destination += it } }
     return destination
 }
 
 inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
-    get(key)?.let { return it }
+    get(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
index ff76a47..6fe4718 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -33,8 +33,7 @@
 
     operator fun contains(key: K): Boolean = map.containsKey(key)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(key: K): I? = map.get(key)?.get()
+    @Suppress("ReplaceGetOrSet") operator fun get(key: K): I? = map.get(key)?.get()
 
     fun indexOfKey(key: K): Int = map.indexOfKey(key)
 
@@ -55,7 +54,9 @@
 class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
     map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
 ) : IndexedReferenceMap<K, I, M>(map) {
-    constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this(
+    constructor(
+        indexedReferenceMap: IndexedReferenceMap<K, I, M>
+    ) : this(
         ArrayMap(indexedReferenceMap.map).apply {
             for (i in 0 until size) {
                 setValueAt(i, valueAt(i).toImmutable())
@@ -63,8 +64,7 @@
         }
     )
 
-    @Suppress("ReplaceGetOrSet")
-    fun mutate(key: K): M? = map.get(key)?.mutate()
+    @Suppress("ReplaceGetOrSet") fun mutate(key: K): M? = map.get(key)?.mutate()
 
     fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get()
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
index 22b4d52..43a902b 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
@@ -72,7 +72,9 @@
     key: K,
     defaultValue: () -> M
 ): M {
-    mutate(key)?.let { return it }
+    mutate(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
index 547e56c..cbc24b1 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -18,12 +18,8 @@
 
 import android.util.ArraySet
 
-/**
- * Immutable set with index-based access.
- */
-sealed class IndexedSet<T>(
-    internal val set: ArraySet<T>
-) : Immutable<MutableIndexedSet<T>> {
+/** Immutable set with index-based access. */
+sealed class IndexedSet<T>(internal val set: ArraySet<T>) : Immutable<MutableIndexedSet<T>> {
     val size: Int
         get() = set.size
 
@@ -40,12 +36,8 @@
     override fun toString(): String = set.toString()
 }
 
-/**
- * Mutable set with index-based access.
- */
-class MutableIndexedSet<T>(
-    set: ArraySet<T> = ArraySet()
-) : IndexedSet<T>(set) {
+/** Mutable set with index-based access. */
+class MutableIndexedSet<T>(set: ArraySet<T> = ArraySet()) : IndexedSet<T>(set) {
     constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set))
 
     fun add(element: T): Boolean = set.add(element)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
index 7ed29e8..e9a405f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
@@ -18,12 +18,8 @@
 
 import android.util.SparseArray
 
-/**
- * Immutable map with index-based access and [Int] keys.
- */
-sealed class IntMap<T>(
-    internal val array: SparseArray<T>
-) : Immutable<MutableIntMap<T>> {
+/** Immutable map with index-based access and [Int] keys. */
+sealed class IntMap<T>(internal val array: SparseArray<T>) : Immutable<MutableIntMap<T>> {
     val size: Int
         get() = array.size()
 
@@ -44,12 +40,8 @@
     override fun toString(): String = array.toString()
 }
 
-/**
- * Mutable map with index-based access and [Int] keys.
- */
-class MutableIntMap<T>(
-    array: SparseArray<T> = SparseArray()
-) : IntMap<T>(array) {
+/** Mutable map with index-based access and [Int] keys. */
+class MutableIntMap<T>(array: SparseArray<T> = SparseArray()) : IntMap<T>(array) {
     constructor(intMap: IntMap<T>) : this(intMap.array.clone())
 
     fun put(key: Int, value: T): T? = array.putReturnOld(key, value)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
index 9aa0a41..09d7319 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
@@ -36,7 +36,9 @@
 
 inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
     forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
+        transform(index, key, value)?.let {
+            return it
+        }
     }
     return null
 }
@@ -72,7 +74,9 @@
 }
 
 inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
-    get(key)?.let { return it }
+    get(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
index 160b227..3f26517 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
@@ -33,8 +33,7 @@
 
     operator fun contains(key: Int): Boolean = array.contains(key)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(key: Int): I? = array.get(key)?.get()
+    @Suppress("ReplaceGetOrSet") operator fun get(key: Int): I? = array.get(key)?.get()
 
     fun indexOfKey(key: Int): Int = array.indexOfKey(key)
 
@@ -55,7 +54,9 @@
 class MutableIntReferenceMap<I : Immutable<M>, M : I>(
     array: SparseArray<MutableReference<I, M>> = SparseArray()
 ) : IntReferenceMap<I, M>(array) {
-    constructor(intReferenceMap: IntReferenceMap<I, M>) : this(
+    constructor(
+        intReferenceMap: IntReferenceMap<I, M>
+    ) : this(
         intReferenceMap.array.clone().apply {
             for (i in 0 until size()) {
                 setValueAt(i, valueAt(i).toImmutable())
@@ -63,8 +64,7 @@
         }
     )
 
-    @Suppress("ReplaceGetOrSet")
-    fun mutate(key: Int): M? = array.get(key)?.mutate()
+    @Suppress("ReplaceGetOrSet") fun mutate(key: Int): M? = array.get(key)?.mutate()
 
     fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get()
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
index 1ed4f8a..a1bab95 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
@@ -72,7 +72,9 @@
     key: Int,
     defaultValue: () -> M
 ): M {
-    mutate(key)?.let { return it }
+    mutate(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
index 21f2af2..1254797 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
@@ -18,12 +18,8 @@
 
 import android.util.SparseBooleanArray
 
-/**
- * Immutable set with index-based access and [Int] elements.
- */
-sealed class IntSet(
-    internal val array: SparseBooleanArray
-) : Immutable<MutableIntSet> {
+/** Immutable set with index-based access and [Int] elements. */
+sealed class IntSet(internal val array: SparseBooleanArray) : Immutable<MutableIntSet> {
     val size: Int
         get() = array.size()
 
@@ -40,12 +36,8 @@
     override fun toString(): String = array.toString()
 }
 
-/**
- * Mutable set with index-based access and [Int] elements.
- */
-class MutableIntSet(
-    array: SparseBooleanArray = SparseBooleanArray()
-) : IntSet(array) {
+/** Mutable set with index-based access and [Int] elements. */
+class MutableIntSet(array: SparseBooleanArray = SparseBooleanArray()) : IntSet(array) {
     constructor(intSet: IntSet) : this(intSet.array.clone())
 
     fun add(element: Int): Boolean =
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
index 163ebbf..9d0d14f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
@@ -66,7 +66,7 @@
 
 operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element }
 
-fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values }
+fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply { this += values }
 
 operator fun MutableIntSet.plusAssign(element: Int) {
     array.put(element, true)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
index 171cfeb..471a71b 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -27,21 +27,17 @@
  * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate]
  * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the
  * data structure is mutated/copied, a new instance of this class should be obtained with
- * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents
- * further modifications to a data structure accessed with its immutable interface.
+ * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents further
+ * modifications to a data structure accessed with its immutable interface.
  *
  * @see MutableIndexedReferenceMap
  * @see MutableIntReferenceMap
  */
-class MutableReference<I : Immutable<M>, M : I> private constructor(
-    private var immutable: I,
-    private var mutable: M?
-) {
+class MutableReference<I : Immutable<M>, M : I>
+private constructor(private var immutable: I, private var mutable: M?) {
     constructor(mutable: M) : this(mutable, mutable)
 
-    /**
-     * Return an immutable reference to the wrapped mutable data structure.
-     */
+    /** Return an immutable reference to the wrapped mutable data structure. */
     fun get(): I = immutable
 
     /**
@@ -50,7 +46,9 @@
      * already mutable.
      */
     fun mutate(): M {
-        mutable?.let { return it }
+        mutable?.let {
+            return it
+        }
         return immutable.toMutable().also {
             immutable = it
             mutable = it
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
index 691ed8f..2983895 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
@@ -23,9 +23,7 @@
 import com.android.server.permission.access.util.PackageVersionMigration
 import com.android.server.pm.permission.PermissionMigrationHelper
 
-/**
- * This class migrate legacy permissions to unified permission subsystem
- */
+/** This class migrate legacy permissions to unified permission subsystem */
 class AppIdPermissionMigration {
     internal fun migrateSystemState(state: MutableAccessState) {
         val legacyPermissionsManager =
@@ -34,10 +32,15 @@
             return
         }
 
-        migratePermissions(state.mutateSystemState().mutatePermissions(),
-            legacyPermissionsManager.legacyPermissions)
-        migratePermissions(state.mutateSystemState().mutatePermissionTrees(),
-            legacyPermissionsManager.legacyPermissionTrees, true)
+        migratePermissions(
+            state.mutateSystemState().mutatePermissions(),
+            legacyPermissionsManager.legacyPermissions
+        )
+        migratePermissions(
+            state.mutateSystemState().mutatePermissionTrees(),
+            legacyPermissionsManager.legacyPermissionTrees,
+            true
+        )
     }
 
     private fun migratePermissions(
@@ -46,14 +49,15 @@
         isPermissionTree: Boolean = false
     ) {
         legacyPermissions.forEach { (_, legacyPermission) ->
-            val permission = Permission(
-                legacyPermission.permissionInfo, false, legacyPermission.type, 0
-            )
+            val permission =
+                Permission(legacyPermission.permissionInfo, false, legacyPermission.type, 0)
             permissions[permission.name] = permission
             if (DEBUG_MIGRATION) {
-                Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " +
-                    "${permission.type}, appId: ${permission.appId}, protectionLevel: " +
-                    "${permission.protectionLevel}, tree: $isPermissionTree"
+                Slog.v(
+                    LOG_TAG,
+                    "Migrated permission: ${permission.name}, type: " +
+                        "${permission.type}, appId: ${permission.appId}, protectionLevel: " +
+                        "${permission.protectionLevel}, tree: $isPermissionTree"
                 )
             }
         }
@@ -81,25 +85,23 @@
 
             val permissionFlags = MutableIndexedMap<String, Int>()
             appIdPermissionFlags[appId] = permissionFlags
-            legacyPermissionStates.forEach forEachPermission@ {
+            legacyPermissionStates.forEach forEachPermission@{
                 (permissionName, legacyPermissionState) ->
                 val permission = state.systemState.permissions[permissionName]
                 if (permission == null) {
                     Slog.w(
-                        LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" +
+                        LOG_TAG,
+                        "Dropping unknown permission $permissionName for app ID $appId" +
                             " when migrating permission state"
                     )
                     return@forEachPermission
                 }
-                permissionFlags[permissionName] = migratePermissionFlags(
-                    permission, legacyPermissionState, appId, userId
-                )
+                permissionFlags[permissionName] =
+                    migratePermissionFlags(permission, legacyPermissionState, appId, userId)
             }
 
             val packageVersions = userState.mutatePackageVersions()
-            packageNames.forEachIndexed { _, packageName ->
-                packageVersions[packageName] = version
-            }
+            packageNames.forEachIndexed { _, packageName -> packageVersions[packageName] = version }
         }
     }
 
@@ -109,29 +111,35 @@
         appId: Int,
         userId: Int
     ): Int {
-        var flags = when {
-            permission.isNormal -> if (legacyPermissionState.isGranted) {
-                PermissionFlags.INSTALL_GRANTED
-            } else {
-                PermissionFlags.INSTALL_REVOKED
-            }
-            permission.isSignature || permission.isInternal ->
-                if (legacyPermissionState.isGranted) {
-                    if (permission.isDevelopment || permission.isRole) {
-                        PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+        var flags =
+            when {
+                permission.isNormal ->
+                    if (legacyPermissionState.isGranted) {
+                        PermissionFlags.INSTALL_GRANTED
                     } else {
-                        PermissionFlags.PROTECTION_GRANTED
+                        PermissionFlags.INSTALL_REVOKED
                     }
-                } else {
-                    0
-                }
-            permission.isRuntime ->
-                if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
-            else -> 0
-        }
-        flags = PermissionFlags.updateFlags(
-            permission, flags, legacyPermissionState.flags, legacyPermissionState.flags
-        )
+                permission.isSignature || permission.isInternal ->
+                    if (legacyPermissionState.isGranted) {
+                        if (permission.isDevelopment || permission.isRole) {
+                            PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+                        } else {
+                            PermissionFlags.PROTECTION_GRANTED
+                        }
+                    } else {
+                        0
+                    }
+                permission.isRuntime ->
+                    if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
+                else -> 0
+            }
+        flags =
+            PermissionFlags.updateFlags(
+                permission,
+                flags,
+                legacyPermissionState.flags,
+                legacyPermissionState.flags
+            )
         if (DEBUG_MIGRATION) {
             val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags)
             val newFlagString = PermissionFlags.toString(flags)
@@ -139,7 +147,8 @@
             val newGrantState = PermissionFlags.isPermissionGranted(flags)
             val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags)
             Slog.v(
-                LOG_TAG, "Migrated appId: $appId, permission: " +
+                LOG_TAG,
+                "Migrated appId: $appId, permission: " +
                     "${permission.name}, user: $userId, oldGrantState: $oldGrantState" +
                     ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " +
                     "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch"
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
index 2c8175b..1f40f01 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
@@ -57,11 +57,12 @@
         isPermissionTree: Boolean
     ) {
         val systemState = state.mutateSystemState(WriteMode.NONE)
-        val permissions = if (isPermissionTree) {
-            systemState.mutatePermissionTrees()
-        } else {
-            systemState.mutatePermissions()
-        }
+        val permissions =
+            if (isPermissionTree) {
+                systemState.mutatePermissionTrees()
+            } else {
+                systemState.mutatePermissions()
+            }
         forEachTag {
             when (val tagName = tagName) {
                 TAG_PERMISSION -> parsePermission(permissions)
@@ -71,10 +72,13 @@
         permissions.forEachReversedIndexed { permissionIndex, _, permission ->
             val packageName = permission.packageName
             val externalState = state.externalState
-            if (packageName !in externalState.packageStates &&
-                packageName !in externalState.disabledSystemPackageStates) {
+            if (
+                packageName !in externalState.packageStates &&
+                    packageName !in externalState.disabledSystemPackageStates
+            ) {
                 Slog.w(
-                    LOG_TAG, "Dropping permission ${permission.name} from unknown package" +
+                    LOG_TAG,
+                    "Dropping permission ${permission.name} from unknown package" +
                         " $packageName when parsing permissions"
                 )
                 permissions.removeAt(permissionIndex)
@@ -88,11 +92,12 @@
     ) {
         val name = getAttributeValueOrThrow(ATTR_NAME).intern()
         @Suppress("DEPRECATION")
-        val permissionInfo = PermissionInfo().apply {
-            this.name = name
-            packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
-            protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
-        }
+        val permissionInfo =
+            PermissionInfo().apply {
+                this.name = name
+                packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
+                protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
+            }
         val type = getAttributeIntOrThrow(ATTR_TYPE)
         when (type) {
             Permission.TYPE_MANIFEST -> {}
@@ -125,15 +130,14 @@
         tagName: String,
         permissions: IndexedMap<String, Permission>
     ) {
-        tag(tagName) {
-            permissions.forEachIndexed { _, _, it -> serializePermission(it) }
-        }
+        tag(tagName) { permissions.forEachIndexed { _, _, it -> serializePermission(it) } }
     }
 
     private fun BinaryXmlSerializer.serializePermission(permission: Permission) {
         val type = permission.type
         when (type) {
-            Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
+            Permission.TYPE_MANIFEST,
+            Permission.TYPE_DYNAMIC -> {}
             Permission.TYPE_CONFIG -> return
             else -> {
                 Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
@@ -228,11 +232,12 @@
         tag(TAG_PERMISSION) {
             attributeInterned(ATTR_NAME, name)
             // Never serialize one-time permissions as granted.
-            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
-                flags andInv PermissionFlags.RUNTIME_GRANTED
-            } else {
-                flags
-            }
+            val serializedFlags =
+                if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+                    flags andInv PermissionFlags.RUNTIME_GRANTED
+                } else {
+                    flags
+                }
             attributeInt(ATTR_FLAGS, serializedFlags)
         }
     }
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 345f101..08ba753 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
@@ -55,7 +55,8 @@
 
     @Volatile
     private var onPermissionFlagsChangedListeners:
-        IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet()
+        IndexedListSet<OnPermissionFlagsChangedListener> =
+        MutableIndexedListSet()
     private val onPermissionFlagsChangedListenersLock = Any()
 
     private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
@@ -73,30 +74,37 @@
     override fun MutateStateScope.onInitialized() {
         newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) ->
             val oldPermission = newState.systemState.permissions[permissionName]
-            val newPermission = if (oldPermission != null) {
-                if (permissionEntry.gids != null) {
-                    oldPermission.copy(
-                        gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
-                    )
+            val newPermission =
+                if (oldPermission != null) {
+                    if (permissionEntry.gids != null) {
+                        oldPermission.copy(
+                            gids = permissionEntry.gids,
+                            areGidsPerUser = permissionEntry.perUser
+                        )
+                    } else {
+                        return@forEach
+                    }
                 } else {
-                    return@forEach
+                    @Suppress("DEPRECATION")
+                    val permissionInfo =
+                        PermissionInfo().apply {
+                            name = permissionName
+                            packageName = PLATFORM_PACKAGE_NAME
+                            protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+                        }
+                    if (permissionEntry.gids != null) {
+                        Permission(
+                            permissionInfo,
+                            false,
+                            Permission.TYPE_CONFIG,
+                            0,
+                            permissionEntry.gids,
+                            permissionEntry.perUser
+                        )
+                    } else {
+                        Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
+                    }
                 }
-            } else {
-                @Suppress("DEPRECATION")
-                val permissionInfo = PermissionInfo().apply {
-                    name = permissionName
-                    packageName = PLATFORM_PACKAGE_NAME
-                    protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
-                }
-                if (permissionEntry.gids != null) {
-                    Permission(
-                        permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
-                        permissionEntry.perUser
-                    )
-                } else {
-                    Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
-                }
-            }
             newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
         }
     }
@@ -200,30 +208,32 @@
         val androidPackage = packageState.androidPackage ?: return
         val appId = packageState.appId
         androidPackage.requestedPermissions.forEach { permissionName ->
-            val permission = newState.systemState.permissions[permissionName]
-                ?: return@forEach
+            val permission = newState.systemState.permissions[permissionName] ?: return@forEach
             if (!permission.isHardOrSoftRestricted) {
                 return@forEach
             }
-            val isRequestedBySystemPackage = anyPackageInAppId(appId) {
-                it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
-            }
+            val isRequestedBySystemPackage =
+                anyPackageInAppId(appId) {
+                    it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
+                }
             if (isRequestedBySystemPackage) {
                 return@forEach
             }
             val oldFlags = getPermissionFlags(appId, userId, permissionName)
             var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
             val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-            newFlags = if (permission.isHardRestricted && !isExempt) {
-                newFlags or PermissionFlags.RESTRICTION_REVOKED
-            } else {
-                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-            }
-            newFlags = if (permission.isSoftRestricted && !isExempt) {
-                newFlags or PermissionFlags.SOFT_RESTRICTED
-            } else {
-                newFlags andInv PermissionFlags.SOFT_RESTRICTED
-            }
+            newFlags =
+                if (permission.isHardRestricted && !isExempt) {
+                    newFlags or PermissionFlags.RESTRICTION_REVOKED
+                } else {
+                    newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+                }
+            newFlags =
+                if (permission.isSoftRestricted && !isExempt) {
+                    newFlags or PermissionFlags.SOFT_RESTRICTED
+                } else {
+                    newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         }
     }
@@ -243,15 +253,15 @@
         val androidPackage = packageState.androidPackage ?: return
         val appId = packageState.appId
         androidPackage.requestedPermissions.forEach { permissionName ->
-            val permission = newState.systemState.permissions[permissionName]
-                ?: return@forEach
+            val permission = newState.systemState.permissions[permissionName] ?: return@forEach
             if (!permission.isRuntime || permission.isRemoved) {
                 return@forEach
             }
-            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
-                it.packageName != packageName &&
-                    permissionName in it.androidPackage!!.requestedPermissions
-            }
+            val isRequestedByOtherPackages =
+                anyPackageInAppId(appId) {
+                    it.packageName != packageName &&
+                        permissionName in it.androidPackage!!.requestedPermissions
+                }
             if (isRequestedByOtherPackages) {
                 return@forEach
             }
@@ -260,13 +270,15 @@
                 return@forEach
             }
             var newFlags = oldFlags
-            newFlags = if (
-                newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT)
-            ) {
-                newFlags or PermissionFlags.RUNTIME_GRANTED
-            } else {
-                newFlags andInv PermissionFlags.RUNTIME_GRANTED
-            }
+            newFlags =
+                if (
+                    newFlags.hasBits(PermissionFlags.ROLE) ||
+                        newFlags.hasBits(PermissionFlags.PREGRANT)
+                ) {
+                    newFlags or PermissionFlags.RUNTIME_GRANTED
+                } else {
+                    newFlags andInv PermissionFlags.RUNTIME_GRANTED
+                }
             newFlags = newFlags andInv USER_SETTABLE_MASK
             if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
                 newFlags = newFlags or PermissionFlags.IMPLICIT
@@ -285,24 +297,32 @@
             if (!canAdoptPermissions(packageName, originalPackageName)) {
                 return@forEachIndexed
             }
-            newState.systemState.permissions.forEachIndexed permissions@ {
-                permissionIndex, permissionName, oldPermission ->
+            newState.systemState.permissions.forEachIndexed permissions@{
+                permissionIndex,
+                permissionName,
+                oldPermission ->
                 if (oldPermission.packageName != originalPackageName) {
                     return@permissions
                 }
                 @Suppress("DEPRECATION")
-                val newPermissionInfo = PermissionInfo().apply {
-                    name = oldPermission.permissionInfo.name
-                    this.packageName = packageName
-                    protectionLevel = oldPermission.permissionInfo.protectionLevel
-                }
+                val newPermissionInfo =
+                    PermissionInfo().apply {
+                        name = oldPermission.permissionInfo.name
+                        this.packageName = packageName
+                        protectionLevel = oldPermission.permissionInfo.protectionLevel
+                    }
                 // Different from the old implementation, which removes the GIDs upon permission
                 // adoption, but adds them back on the next boot, we now just consistently keep the
                 // GIDs.
-                val newPermission = oldPermission.copy(
-                    permissionInfo = newPermissionInfo, isReconciled = false, appId = 0
-                )
-                newState.mutateSystemState().mutatePermissions()
+                val newPermission =
+                    oldPermission.copy(
+                        permissionInfo = newPermissionInfo,
+                        isReconciled = false,
+                        appId = 0
+                    )
+                newState
+                    .mutateSystemState()
+                    .mutatePermissions()
                     .putAt(permissionIndex, newPermission)
                 changedPermissionNames += permissionName
             }
@@ -313,18 +333,20 @@
         packageName: String,
         originalPackageName: String
     ): Boolean {
-        val originalPackageState = newState.externalState.packageStates[originalPackageName]
-            ?: return false
+        val originalPackageState =
+            newState.externalState.packageStates[originalPackageName] ?: return false
         if (!originalPackageState.isSystem) {
             Slog.w(
-                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                LOG_TAG,
+                "Unable to adopt permissions from $originalPackageName to $packageName:" +
                     " original package not in system partition"
             )
             return false
         }
         if (originalPackageState.androidPackage != null) {
             Slog.w(
-                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                LOG_TAG,
+                "Unable to adopt permissions from $originalPackageName to $packageName:" +
                     " original package still exists"
             )
             return false
@@ -339,20 +361,25 @@
         val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
         if (isInstantApp) {
             Slog.w(
-                LOG_TAG, "Ignoring permission groups declared in package" +
+                LOG_TAG,
+                "Ignoring permission groups declared in package" +
                     " ${packageState.packageName}: instant apps cannot declare permission groups"
             )
             return
         }
         packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
-            val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
-                parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
-            )!!
+            val newPermissionGroup =
+                PackageInfoUtils.generatePermissionGroupInfo(
+                    parsedPermissionGroup,
+                    PackageManager.GET_META_DATA.toLong()
+                )!!
             // TODO: Clear permission state on group take-over?
             val permissionGroupName = newPermissionGroup.name
             val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
-            if (oldPermissionGroup != null &&
-                newPermissionGroup.packageName != oldPermissionGroup.packageName) {
+            if (
+                oldPermissionGroup != null &&
+                    newPermissionGroup.packageName != oldPermissionGroup.packageName
+            ) {
                 val newPackageName = newPermissionGroup.packageName
                 val oldPackageName = oldPermissionGroup.packageName
                 // Different from the old implementation, which defines permission group on
@@ -361,7 +388,8 @@
                 // to permissions so that we no longer need to rely on the scan order.
                 if (!packageState.isSystem) {
                     Slog.w(
-                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
+                        LOG_TAG,
+                        "Ignoring permission group $permissionGroupName declared in" +
                             " package $newPackageName: already declared in another" +
                             " package $oldPackageName"
                     )
@@ -369,14 +397,16 @@
                 }
                 if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) {
                     Slog.w(
-                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
+                        LOG_TAG,
+                        "Ignoring permission group $permissionGroupName declared in" +
                             " system package $newPackageName: already declared in another" +
                             " system package $oldPackageName"
                     )
                     return@forEachIndexed
                 }
                 Slog.w(
-                    LOG_TAG, "Overriding permission group $permissionGroupName with" +
+                    LOG_TAG,
+                    "Overriding permission group $permissionGroupName with" +
                         " new declaration in system package $newPackageName: originally" +
                         " declared in another package $oldPackageName"
                 )
@@ -393,20 +423,23 @@
         val androidPackage = packageState.androidPackage!!
         // This may not be the same package as the old permission because the old permission owner
         // can be different, hence using this somewhat strange name to prevent misuse.
-        val oldNewPackage = oldState.externalState.packageStates[packageState.packageName]
-            ?.androidPackage
-        val isPackageSigningChanged = oldNewPackage != null &&
-                androidPackage.signingDetails != oldNewPackage.signingDetails
+        val oldNewPackage =
+            oldState.externalState.packageStates[packageState.packageName]?.androidPackage
+        val isPackageSigningChanged =
+            oldNewPackage != null && androidPackage.signingDetails != oldNewPackage.signingDetails
         androidPackage.permissions.forEachIndexed { _, parsedPermission ->
-            val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
-                parsedPermission, PackageManager.GET_META_DATA.toLong()
-            )!!
+            val newPermissionInfo =
+                PackageInfoUtils.generatePermissionInfo(
+                    parsedPermission,
+                    PackageManager.GET_META_DATA.toLong()
+                )!!
             val permissionName = newPermissionInfo.name
-            val oldPermission = if (parsedPermission.isTree) {
-                newState.systemState.permissionTrees[permissionName]
-            } else {
-                newState.systemState.permissions[permissionName]
-            }
+            val oldPermission =
+                if (parsedPermission.isTree) {
+                    newState.systemState.permissionTrees[permissionName]
+                } else {
+                    newState.systemState.permissions[permissionName]
+                }
             // Different from the old implementation, which may add an (incomplete) signature
             // permission inside another package's permission tree, we now consistently ignore such
             // permissions.
@@ -414,128 +447,152 @@
             val newPackageName = newPermissionInfo.packageName
             if (permissionTree != null && newPackageName != permissionTree.packageName) {
                 Slog.w(
-                    LOG_TAG, "Ignoring permission $permissionName declared in package" +
+                    LOG_TAG,
+                    "Ignoring permission $permissionName declared in package" +
                         " $newPackageName: base permission tree ${permissionTree.name} is" +
                         " declared in another package ${permissionTree.packageName}"
                 )
                 return@forEachIndexed
             }
-            val newPermission = if (oldPermission != null &&
-                newPackageName != oldPermission.packageName) {
-                val oldPackageName = oldPermission.packageName
-                // Only allow system apps to redefine non-system permissions.
-                if (!packageState.isSystem) {
-                    Slog.w(
-                        LOG_TAG, "Ignoring permission $permissionName declared in package" +
-                            " $newPackageName: already declared in another package" +
-                            " $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-                if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
-                    // It's a config permission and has no owner, take ownership now.
-                    oldPermission.copy(
-                        permissionInfo = newPermissionInfo, isReconciled = true,
-                        type = Permission.TYPE_MANIFEST, appId = packageState.appId
-                    )
-                } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) {
-                    Slog.w(
-                        LOG_TAG, "Overriding permission $permissionName with new declaration in" +
-                            " system package $newPackageName: originally declared in another" +
-                            " package $oldPackageName"
-                    )
-                    // Remove permission state on owner change.
-                    newState.externalState.userIds.forEachIndexed { _, userId ->
-                        newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
-                            setPermissionFlags(appId, userId, permissionName, 0)
-                        }
-                    }
-                    // Different from the old implementation, which removes the GIDs upon permission
-                    // override, but adds them back on the next boot, we now just consistently keep
-                    // the GIDs.
-                    Permission(
-                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId,
-                        oldPermission.gids, oldPermission.areGidsPerUser
-                    )
-                } else {
-                    Slog.w(
-                        LOG_TAG, "Ignoring permission $permissionName declared in system package" +
-                            " $newPackageName: already declared in another system package" +
-                            " $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-            } else {
-                if (oldPermission != null && oldPermission.isReconciled) {
-                    val isPermissionGroupChanged = newPermissionInfo.isRuntime &&
-                        newPermissionInfo.group != null &&
-                        newPermissionInfo.group != oldPermission.groupName
-                    val isPermissionProtectionChanged =
-                        oldPermission.type != Permission.TYPE_CONFIG && (
-                            (newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
-                                (newPermissionInfo.isInternal && !oldPermission.isInternal)
+            val newPermission =
+                if (oldPermission != null && newPackageName != oldPermission.packageName) {
+                    val oldPackageName = oldPermission.packageName
+                    // Only allow system apps to redefine non-system permissions.
+                    if (!packageState.isSystem) {
+                        Slog.w(
+                            LOG_TAG,
+                            "Ignoring permission $permissionName declared in package" +
+                                " $newPackageName: already declared in another package" +
+                                " $oldPackageName"
                         )
-                    if (isPermissionGroupChanged || isPermissionProtectionChanged) {
+                        return@forEachIndexed
+                    }
+                    if (
+                        oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled
+                    ) {
+                        // It's a config permission and has no owner, take ownership now.
+                        oldPermission.copy(
+                            permissionInfo = newPermissionInfo,
+                            isReconciled = true,
+                            type = Permission.TYPE_MANIFEST,
+                            appId = packageState.appId
+                        )
+                    } else if (
+                        newState.externalState.packageStates[oldPackageName]?.isSystem != true
+                    ) {
+                        Slog.w(
+                            LOG_TAG,
+                            "Overriding permission $permissionName with new declaration in" +
+                                " system package $newPackageName: originally declared in another" +
+                                " package $oldPackageName"
+                        )
+                        // Remove permission state on owner change.
                         newState.externalState.userIds.forEachIndexed { _, userId ->
                             newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
-                                if (isPermissionGroupChanged) {
-                                    // We might auto-grant permissions if any permission of
-                                    // the group is already granted. Hence if the group of
-                                    // a granted permission changes we need to revoke it to
-                                    // avoid having permissions of the new group auto-granted.
-                                    Slog.w(
-                                        LOG_TAG, "Revoking runtime permission $permissionName for" +
-                                            " appId $appId and userId $userId as the permission" +
-                                            " group changed from ${oldPermission.groupName}" +
-                                            " to ${newPermissionInfo.group}"
-                                    )
-                                }
-                                if (isPermissionProtectionChanged) {
-                                    Slog.w(
-                                        LOG_TAG, "Revoking permission $permissionName for" +
-                                            " appId $appId and userId $userId as the permission" +
-                                            " protection changed."
-                                    )
-                                }
                                 setPermissionFlags(appId, userId, permissionName, 0)
                             }
                         }
+                        // Different from the old implementation, which removes the GIDs upon
+                        // permission
+                        // override, but adds them back on the next boot, we now just consistently
+                        // keep
+                        // the GIDs.
+                        Permission(
+                            newPermissionInfo,
+                            true,
+                            Permission.TYPE_MANIFEST,
+                            packageState.appId,
+                            oldPermission.gids,
+                            oldPermission.areGidsPerUser
+                        )
+                    } else {
+                        Slog.w(
+                            LOG_TAG,
+                            "Ignoring permission $permissionName declared in system package" +
+                                " $newPackageName: already declared in another system package" +
+                                " $oldPackageName"
+                        )
+                        return@forEachIndexed
+                    }
+                } else {
+                    if (oldPermission != null && oldPermission.isReconciled) {
+                        val isPermissionGroupChanged =
+                            newPermissionInfo.isRuntime &&
+                                newPermissionInfo.group != null &&
+                                newPermissionInfo.group != oldPermission.groupName
+                        val isPermissionProtectionChanged =
+                            oldPermission.type != Permission.TYPE_CONFIG &&
+                                ((newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
+                                    (newPermissionInfo.isInternal && !oldPermission.isInternal))
+                        if (isPermissionGroupChanged || isPermissionProtectionChanged) {
+                            newState.externalState.userIds.forEachIndexed { _, userId ->
+                                newState.externalState.appIdPackageNames.forEachIndexed {
+                                    _,
+                                    appId,
+                                    _ ->
+                                    if (isPermissionGroupChanged) {
+                                        // We might auto-grant permissions if any permission of
+                                        // the group is already granted. Hence if the group of
+                                        // a granted permission changes we need to revoke it to
+                                        // avoid having permissions of the new group auto-granted.
+                                        Slog.w(
+                                            LOG_TAG,
+                                            "Revoking runtime permission $permissionName for" +
+                                                " appId $appId and userId $userId as the permission" +
+                                                " group changed from ${oldPermission.groupName}" +
+                                                " to ${newPermissionInfo.group}"
+                                        )
+                                    }
+                                    if (isPermissionProtectionChanged) {
+                                        Slog.w(
+                                            LOG_TAG,
+                                            "Revoking permission $permissionName for" +
+                                                " appId $appId and userId $userId as the permission" +
+                                                " protection changed."
+                                        )
+                                    }
+                                    setPermissionFlags(appId, userId, permissionName, 0)
+                                }
+                            }
+                        }
+                    }
+
+                    // Different from the old implementation, which doesn't update the permission
+                    // definition upon app update, but does update it on the next boot, we now
+                    // consistently update the permission definition upon app update.
+                    @Suppress("IfThenToElvis")
+                    if (oldPermission != null) {
+                        oldPermission.copy(
+                            permissionInfo = newPermissionInfo,
+                            isReconciled = true,
+                            type = Permission.TYPE_MANIFEST,
+                            appId = packageState.appId
+                        )
+                    } else {
+                        Permission(
+                            newPermissionInfo,
+                            true,
+                            Permission.TYPE_MANIFEST,
+                            packageState.appId
+                        )
                     }
                 }
 
-                // Different from the old implementation, which doesn't update the permission
-                // definition upon app update, but does update it on the next boot, we now
-                // consistently update the permission definition upon app update.
-                @Suppress("IfThenToElvis")
-                if (oldPermission != null) {
-                    oldPermission.copy(
-                        permissionInfo = newPermissionInfo, isReconciled = true,
-                        type = Permission.TYPE_MANIFEST, appId = packageState.appId
-                    )
-                } else {
-                    Permission(
-                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
-                    )
-                }
-            }
-
             if (parsedPermission.isTree) {
                 newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission
             } else {
                 newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
-                val isPermissionChanged = oldPermission == null ||
-                    newPackageName != oldPermission.packageName ||
-                    newPermission.protectionLevel != oldPermission.protectionLevel || (
-                        oldPermission.isReconciled && (
-                            (newPermission.isSignature && isPackageSigningChanged) || (
-                                newPermission.isKnownSigner &&
-                                    newPermission.knownCerts != oldPermission.knownCerts
-                            ) || (
-                                newPermission.isRuntime && newPermission.groupName != null &&
-                                    newPermission.groupName != oldPermission.groupName
-                            )
-                        )
-                    )
+                val isPermissionChanged =
+                    oldPermission == null ||
+                        newPackageName != oldPermission.packageName ||
+                        newPermission.protectionLevel != oldPermission.protectionLevel ||
+                        (oldPermission.isReconciled &&
+                            ((newPermission.isSignature && isPackageSigningChanged) ||
+                                (newPermission.isKnownSigner &&
+                                    newPermission.knownCerts != oldPermission.knownCerts) ||
+                                (newPermission.isRuntime &&
+                                    newPermission.groupName != null &&
+                                    newPermission.groupName != oldPermission.groupName)))
                 if (isPermissionChanged) {
                     changedPermissionNames += permissionName
                 }
@@ -552,39 +609,47 @@
         if (packageState != null && androidPackage == null) {
             return
         }
-        val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName]
-            ?.androidPackage
+        val disabledSystemPackage =
+            newState.externalState.disabledSystemPackageStates[packageName]?.androidPackage
         // Unlike in the previous implementation, we now also retain permission trees defined by
         // disabled system packages for consistency with permissions.
         newState.systemState.permissionTrees.forEachReversedIndexed {
-            permissionTreeIndex, permissionTreeName, permissionTree ->
-            if (permissionTree.packageName == packageName && (
-                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
-                    it.isTree && it.name == permissionTreeName
-                }
-            ) && (
-                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
-                    it.isTree && it.name == permissionTreeName
-                } != true
-            )) {
+            permissionTreeIndex,
+            permissionTreeName,
+            permissionTree ->
+            if (
+                permissionTree.packageName == packageName &&
+                    (packageState == null ||
+                        androidPackage!!.permissions.noneIndexed { _, it ->
+                            it.isTree && it.name == permissionTreeName
+                        }) &&
+                    (disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+                        it.isTree && it.name == permissionTreeName
+                    } != true)
+            ) {
                 newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex)
             }
         }
 
         newState.systemState.permissions.forEachReversedIndexed {
-            permissionIndex, permissionName, permission ->
+            permissionIndex,
+            permissionName,
+            permission ->
             val updatedPermission = updatePermissionIfDynamic(permission)
-            newState.mutateSystemState().mutatePermissions()
+            newState
+                .mutateSystemState()
+                .mutatePermissions()
                 .putAt(permissionIndex, updatedPermission)
-            if (updatedPermission.packageName == packageName && (
-                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
-                    !it.isTree && it.name == permissionName
-                }
-            ) && (
-                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
-                    !it.isTree && it.name == permissionName
-                } != true
-            )) {
+            if (
+                updatedPermission.packageName == packageName &&
+                    (packageState == null ||
+                        androidPackage!!.permissions.noneIndexed { _, it ->
+                            !it.isTree && it.name == permissionName
+                        }) &&
+                    (disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+                        !it.isTree && it.name == permissionName
+                    } != true)
+            ) {
                 // Different from the old implementation where we keep the permission state if the
                 // permission is declared by a disabled system package (ag/15189282), we now
                 // shouldn't be notified when the updated system package is removed but the disabled
@@ -608,9 +673,12 @@
         val permissionTree = findPermissionTree(permission.name) ?: return permission
         @Suppress("DEPRECATION")
         return permission.copy(
-            permissionInfo = PermissionInfo(permission.permissionInfo).apply {
-                packageName = permissionTree.packageName
-            }, appId = permissionTree.appId, isReconciled = true
+            permissionInfo =
+                PermissionInfo(permission.permissionInfo).apply {
+                    packageName = permissionTree.packageName
+                },
+            appId = permissionTree.appId,
+            isReconciled = true
         )
     }
 
@@ -636,8 +704,9 @@
     }
 
     private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) {
-        val hasOldPackage = appId in oldState.externalState.appIdPackageNames &&
-            anyPackageInAppId(appId, oldState) { true }
+        val hasOldPackage =
+            appId in oldState.externalState.appIdPackageNames &&
+                anyPackageInAppId(appId, oldState) { true }
         if (!hasOldPackage) {
             // Don't revoke anything if this isn't a package update, i.e. if information about the
             // old package isn't available. Notably, this also means skipping packages changed via
@@ -650,46 +719,58 @@
         // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
         val oldTargetSdkVersion =
             reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) {
-                targetSdkVersion, packageState ->
+                targetSdkVersion,
+                packageState ->
                 targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
             }
         val newTargetSdkVersion =
             reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) {
-                targetSdkVersion, packageState ->
+                targetSdkVersion,
+                packageState ->
                 targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
             }
         @Suppress("ConvertTwoComparisonsToRangeCheck")
-        val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
-            newTargetSdkVersion < Build.VERSION_CODES.Q
+        val isTargetSdkVersionDowngraded =
+            oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
+                newTargetSdkVersion < Build.VERSION_CODES.Q
         @Suppress("ConvertTwoComparisonsToRangeCheck")
-        val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
-            newTargetSdkVersion >= Build.VERSION_CODES.Q
-        val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) {
-            it.androidPackage!!.isRequestLegacyExternalStorage
-        }
-        val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) {
-            it.androidPackage!!.isRequestLegacyExternalStorage
-        }
-        val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
-            !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
-        val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage ||
-            isTargetSdkVersionDowngraded
+        val isTargetSdkVersionUpgraded =
+            oldTargetSdkVersion < Build.VERSION_CODES.Q &&
+                newTargetSdkVersion >= Build.VERSION_CODES.Q
+        val oldIsRequestLegacyExternalStorage =
+            anyPackageInAppId(appId, oldState) {
+                it.androidPackage!!.isRequestLegacyExternalStorage
+            }
+        val newIsRequestLegacyExternalStorage =
+            anyPackageInAppId(appId, newState) {
+                it.androidPackage!!.isRequestLegacyExternalStorage
+            }
+        val isNewlyRequestingLegacyExternalStorage =
+            !isTargetSdkVersionUpgraded &&
+                !oldIsRequestLegacyExternalStorage &&
+                newIsRequestLegacyExternalStorage
+        val shouldRevokeStorageAndMediaPermissions =
+            isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded
         if (shouldRevokeStorageAndMediaPermissions) {
             newState.userStates.forEachIndexed { _, userId, userState ->
                 userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
-                    _, permissionName, oldFlags ->
+                    _,
+                    permissionName,
+                    oldFlags ->
                     // Do not revoke the permission during an upgrade if it's POLICY_FIXED or
                     // SYSTEM_FIXED. Otherwise the user cannot grant back the permission.
-                    if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
-                        oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) &&
-                        !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
+                    if (
+                        permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
+                            oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) &&
+                            !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)
+                    ) {
                         Slog.v(
-                            LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
+                            LOG_TAG,
+                            "Revoking storage permission: $permissionName for appId: " +
                                 " $appId and user: $userId"
                         )
-                        val newFlags = oldFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
-                        )
+                        val newFlags =
+                            oldFlags andInv (PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK)
                         setPermissionFlags(appId, userId, permissionName, newFlags)
                     }
                 }
@@ -704,9 +785,10 @@
         val externalState = newState.externalState
         externalState.userIds.forEachIndexed { _, userId ->
             externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
-                val isPermissionRequested = anyPackageInAppId(appId) {
-                    permissionName in it.androidPackage!!.requestedPermissions
-                }
+                val isPermissionRequested =
+                    anyPackageInAppId(appId) {
+                        permissionName in it.androidPackage!!.requestedPermissions
+                    }
                 if (isPermissionRequested) {
                     evaluatePermissionState(appId, userId, permissionName, installedPackageState)
                 }
@@ -720,7 +802,9 @@
     ) {
         newState.externalState.userIds.forEachIndexed { _, userId ->
             evaluateAllPermissionStatesForPackageAndUser(
-                packageState, userId, installedPackageState
+                packageState,
+                userId,
+                installedPackageState
             )
         }
     }
@@ -732,7 +816,10 @@
     ) {
         packageState.androidPackage?.requestedPermissions?.forEach { permissionName ->
             evaluatePermissionState(
-                packageState.appId, userId, permissionName, installedPackageState
+                packageState.appId,
+                userId,
+                permissionName,
+                installedPackageState
             )
         }
     }
@@ -779,57 +866,71 @@
             val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
             if (!wasGranted) {
                 val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
-                val isRequestedByInstalledPackage = installedPackageState != null &&
-                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
+                val isRequestedByInstalledPackage =
+                    installedPackageState != null &&
+                        permissionName in
+                            installedPackageState.androidPackage!!.requestedPermissions
                 val isRequestedBySystemPackage =
                     requestingPackageStates.anyIndexed { _, it -> it.isSystem }
-                val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it ->
-                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
-                }
+                val isCompatibilityPermission =
+                    requestingPackageStates.anyIndexed { _, it ->
+                        isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
+                    }
                 // If this is an existing, non-system package,
                 // then we can't add any new permissions to it.
                 // Except if this is a permission that was added to the platform
-                var newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
-                    isRequestedBySystemPackage || isCompatibilityPermission) {
-                    PermissionFlags.INSTALL_GRANTED
-                } else {
-                    PermissionFlags.INSTALL_REVOKED
-                }
+                var newFlags =
+                    if (
+                        !wasRevoked ||
+                            isRequestedByInstalledPackage ||
+                            isRequestedBySystemPackage ||
+                            isCompatibilityPermission
+                    ) {
+                        PermissionFlags.INSTALL_GRANTED
+                    } else {
+                        PermissionFlags.INSTALL_REVOKED
+                    }
                 if (permission.isAppOp) {
-                    newFlags = newFlags or (
-                        oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
-                    )
+                    newFlags =
+                        newFlags or
+                            (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET))
                 }
                 setPermissionFlags(appId, userId, permissionName, newFlags)
             }
         } else if (permission.isSignature || permission.isInternal) {
             val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
-            var newFlags = if (hasMissingPackage && wasProtectionGranted) {
-                // Keep the non-runtime permission grants for shared UID with missing androidPackage
-                PermissionFlags.PROTECTION_GRANTED
-            } else {
-                val mayGrantByPrivileged = !permission.isPrivileged ||
-                    requestingPackageStates.anyIndexed { _, it ->
-                        checkPrivilegedPermissionAllowlist(it, permission)
-                    }
-                val shouldGrantBySignature = permission.isSignature &&
-                    requestingPackageStates.anyIndexed { _, it ->
-                        shouldGrantPermissionBySignature(it, permission)
-                    }
-                val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it ->
-                    shouldGrantPermissionByProtectionFlags(it, permission)
-                }
-                if (mayGrantByPrivileged &&
-                    (shouldGrantBySignature || shouldGrantByProtectionFlags)) {
+            var newFlags =
+                if (hasMissingPackage && wasProtectionGranted) {
+                    // Keep the non-runtime permission grants for shared UID with missing
+                    // androidPackage
                     PermissionFlags.PROTECTION_GRANTED
                 } else {
-                    0
+                    val mayGrantByPrivileged =
+                        !permission.isPrivileged ||
+                            requestingPackageStates.anyIndexed { _, it ->
+                                checkPrivilegedPermissionAllowlist(it, permission)
+                            }
+                    val shouldGrantBySignature =
+                        permission.isSignature &&
+                            requestingPackageStates.anyIndexed { _, it ->
+                                shouldGrantPermissionBySignature(it, permission)
+                            }
+                    val shouldGrantByProtectionFlags =
+                        requestingPackageStates.anyIndexed { _, it ->
+                            shouldGrantPermissionByProtectionFlags(it, permission)
+                        }
+                    if (
+                        mayGrantByPrivileged &&
+                            (shouldGrantBySignature || shouldGrantByProtectionFlags)
+                    ) {
+                        PermissionFlags.PROTECTION_GRANTED
+                    } else {
+                        0
+                    }
                 }
-            }
             if (permission.isAppOp) {
-                newFlags = newFlags or (
-                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
-                )
+                newFlags =
+                    newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET))
             }
             // Different from the old implementation, which seemingly allows granting an
             // unallowlisted privileged permission via development or role but revokes it upon next
@@ -840,9 +941,9 @@
                 newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
             }
             if (permission.isRole) {
-                newFlags = newFlags or (
-                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)
-                )
+                newFlags =
+                    newFlags or
+                        (oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED))
             }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         } else if (permission.isRuntime) {
@@ -850,7 +951,9 @@
             val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
             val targetSdkVersion =
                 requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) {
-                    targetSdkVersion, _, packageState ->
+                    targetSdkVersion,
+                    _,
+                    packageState ->
                     targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
                 }
             if (targetSdkVersion < Build.VERSION_CODES.M) {
@@ -883,23 +986,27 @@
                     }
                 }
                 val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
-                val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
-                    permissionName in NOTIFICATIONS_PERMISSIONS
-                val isImplicitPermission = requestingPackageStates.anyIndexed { _, it ->
-                    permissionName in it.androidPackage!!.implicitPermissions
-                }
-                val sourcePermissions = newState.externalState
-                    .implicitToSourcePermissions[permissionName]
-                val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed {
-                    _, sourcePermissionName ->
-                    val sourcePermission = newState.systemState.permissions[sourcePermissionName]
-                    checkNotNull(sourcePermission) {
-                        "Unknown source permission $sourcePermissionName in split permissions"
+                val isLeanbackNotificationsPermission =
+                    newState.externalState.isLeanback && permissionName in NOTIFICATIONS_PERMISSIONS
+                val isImplicitPermission =
+                    requestingPackageStates.anyIndexed { _, it ->
+                        permissionName in it.androidPackage!!.implicitPermissions
                     }
-                    !sourcePermission.isRuntime
-                } ?: false
-                val shouldGrantByImplicit = isLeanbackNotificationsPermission ||
-                    (isImplicitPermission && isAnySourcePermissionNonRuntime)
+                val sourcePermissions =
+                    newState.externalState.implicitToSourcePermissions[permissionName]
+                val isAnySourcePermissionNonRuntime =
+                    sourcePermissions?.anyIndexed { _, sourcePermissionName ->
+                        val sourcePermission =
+                            newState.systemState.permissions[sourcePermissionName]
+                        checkNotNull(sourcePermission) {
+                            "Unknown source permission $sourcePermissionName in split permissions"
+                        }
+                        !sourcePermission.isRuntime
+                    }
+                        ?: false
+                val shouldGrantByImplicit =
+                    isLeanbackNotificationsPermission ||
+                        (isImplicitPermission && isAnySourcePermissionNonRuntime)
                 if (shouldGrantByImplicit) {
                     newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
                     if (wasRevoked) {
@@ -907,26 +1014,31 @@
                     }
                 } else {
                     newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
-                    if ((wasGrantedByLegacy || wasGrantedByImplicit) &&
-                        newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) {
+                    if (
+                        (wasGrantedByLegacy || wasGrantedByImplicit) &&
+                            newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)
+                    ) {
                         // The permission was granted from a compatibility grant or an implicit
                         // grant, however this flag might still be set if the user denied this
                         // permission in the settings. Hence upon app upgrade and when this
                         // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke
                         // the permission, we want to remove this flag so that the app can request
                         // the permission again.
-                        newFlags = newFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED
-                        )
+                        newFlags =
+                            newFlags andInv
+                                (PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED)
                     }
                 }
                 if (!isImplicitPermission && hasImplicitFlag) {
                     newFlags = newFlags andInv PermissionFlags.IMPLICIT
                     var shouldRetainAsNearbyDevices = false
                     if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
-                        val accessBackgroundLocationFlags = getPermissionFlags(
-                            appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
-                        )
+                        val accessBackgroundLocationFlags =
+                            getPermissionFlags(
+                                appId,
+                                userId,
+                                Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                            )
                         shouldRetainAsNearbyDevices =
                             PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
                                 !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
@@ -937,46 +1049,57 @@
                             newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
                         }
                     } else {
-                        newFlags = newFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or
-                                PermissionFlags.USER_FIXED
-                        )
+                        newFlags =
+                            newFlags andInv
+                                (PermissionFlags.RUNTIME_GRANTED or
+                                    PermissionFlags.USER_SET or
+                                    PermissionFlags.USER_FIXED)
                     }
                 }
             }
 
             val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
             val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED)
-            val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) {
-                // All restricted permissions start as exempt. If there's an installer for the
-                // package, we will drop this UPGRADE_EXEMPT flag when we receive the
-                // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags.
-                // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that
-                // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to
-                // apps with missing permission state.
-                // This way we make sure both pre-installed apps, and apps updated/installed after
-                // a rollback snapshot is taken, can get the allowlist for permissions that won't be
-                // allowlisted otherwise.
-                newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT
-                true
-            } else {
-                wasExempt
-            }
-            newFlags = if (permission.isHardRestricted && !isExempt) {
-                newFlags or PermissionFlags.RESTRICTION_REVOKED
-            } else {
-                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-            }
-            newFlags = if (permission.isSoftRestricted && !isExempt) {
-                newFlags or PermissionFlags.SOFT_RESTRICTED
-            } else {
-                newFlags andInv PermissionFlags.SOFT_RESTRICTED
-            }
+            val isExempt =
+                if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) {
+                    // All restricted permissions start as exempt. If there's an installer for the
+                    // package, we will drop this UPGRADE_EXEMPT flag when we receive the
+                    // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags.
+                    // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag
+                    // that
+                    // was assigned to pre-installed apps in RuntimePermissionsUpgradeController,
+                    // and to
+                    // apps with missing permission state.
+                    // This way we make sure both pre-installed apps, and apps updated/installed
+                    // after
+                    // a rollback snapshot is taken, can get the allowlist for permissions that
+                    // won't be
+                    // allowlisted otherwise.
+                    newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT
+                    true
+                } else {
+                    wasExempt
+                }
+            newFlags =
+                if (permission.isHardRestricted && !isExempt) {
+                    newFlags or PermissionFlags.RESTRICTION_REVOKED
+                } else {
+                    newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+                }
+            newFlags =
+                if (permission.isSoftRestricted && !isExempt) {
+                    newFlags or PermissionFlags.SOFT_RESTRICTED
+                } else {
+                    newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         } else {
-            Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
-                "for permission ${permission.name} while evaluating permission state" +
-                "for appId $appId and userId $userId")
+            Slog.e(
+                LOG_TAG,
+                "Unknown protection level ${permission.protectionLevel}" +
+                    "for permission ${permission.name} while evaluating permission state" +
+                    "for appId $appId and userId $userId"
+            )
         }
     }
 
@@ -985,7 +1108,7 @@
         forEachPackageInAppId(appId) {
             implicitPermissions += it.androidPackage!!.implicitPermissions
         }
-        implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName ->
+        implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName ->
             val implicitPermission = newState.systemState.permissions[implicitPermissionName]
             checkNotNull(implicitPermission) {
                 "Unknown implicit permission $implicitPermissionName in split permissions"
@@ -999,10 +1122,11 @@
             if (!isNewPermission) {
                 return@implicitPermissions
             }
-            val sourcePermissions = newState.externalState
-                .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
+            val sourcePermissions =
+                newState.externalState.implicitToSourcePermissions[implicitPermissionName]
+                    ?: return@implicitPermissions
             var newFlags = getPermissionFlags(appId, userId, implicitPermissionName)
-            sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
+            sourcePermissions.forEachIndexed sourcePermissions@{ _, sourcePermissionName ->
                 val sourcePermission = newState.systemState.permissions[sourcePermissionName]
                 checkNotNull(sourcePermission) {
                     "Unknown source permission $sourcePermissionName in split permissions"
@@ -1032,11 +1156,14 @@
         permissionName: String
     ): Boolean {
         for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
-            if (compatibilityPermission.name == permissionName &&
-                androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
+            if (
+                compatibilityPermission.name == permissionName &&
+                    androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion
+            ) {
                 Slog.i(
-                    LOG_TAG, "Auto-granting $permissionName to old package" +
-                    " ${androidPackage.packageName}"
+                    LOG_TAG,
+                    "Auto-granting $permissionName to old package" +
+                        " ${androidPackage.packageName}"
                 )
                 return true
             }
@@ -1058,15 +1185,23 @@
         //     and the defining package still trusts the old certificate for permissions
         // - or it shares the above relationships with the system package
         val packageSigningDetails = packageState.androidPackage!!.signingDetails
-        val sourceSigningDetails = newState.externalState
-            .packageStates[permission.packageName]?.androidPackage?.signingDetails
-        val platformSigningDetails = newState.externalState
-            .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
-        return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
-            SigningDetails.CertCapabilities.PERMISSION) == true ||
+        val sourceSigningDetails =
+            newState.externalState.packageStates[permission.packageName]
+                ?.androidPackage
+                ?.signingDetails
+        val platformSigningDetails =
+            newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!!
+                .androidPackage!!
+                .signingDetails
+        return sourceSigningDetails?.hasCommonSignerWithCapability(
+            packageSigningDetails,
+            SigningDetails.CertCapabilities.PERMISSION
+        ) == true ||
             packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
-            platformSigningDetails.checkCapability(packageSigningDetails,
-                    SigningDetails.CertCapabilities.PERMISSION)
+            platformSigningDetails.checkCapability(
+                packageSigningDetails,
+                SigningDetails.CertCapabilities.PERMISSION
+            )
     }
 
     private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
@@ -1082,8 +1217,9 @@
         if (!(packageState.isSystem && packageState.isPrivileged)) {
             return true
         }
-        if (permission.packageName !in
-            newState.externalState.privilegedPermissionAllowlistPackages) {
+        if (
+            permission.packageName !in newState.externalState.privilegedPermissionAllowlistPackages
+        ) {
             return true
         }
         val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name)
@@ -1099,13 +1235,15 @@
             // Apps that are in updated apex's do not need to be allowlisted
             if (!packageState.isApkInUpdatedApex) {
                 Slog.w(
-                    LOG_TAG, "Privileged permission ${permission.name} for package" +
-                    " ${packageState.packageName} (${packageState.path}) not in" +
-                    " privileged permission allowlist"
+                    LOG_TAG,
+                    "Privileged permission ${permission.name} for package" +
+                        " ${packageState.packageName} (${packageState.path}) not in" +
+                        " privileged permission allowlist"
                 )
                 if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    privilegedPermissionAllowlistViolations += "${packageState.packageName}" +
-                        " (${packageState.path}): ${permission.name}"
+                    privilegedPermissionAllowlistViolations +=
+                        "${packageState.packageName}" +
+                            " (${packageState.path}): ${permission.name}"
                 }
             }
         }
@@ -1124,32 +1262,40 @@
         val apexModuleName = packageState.apexModuleName
         val packageName = packageState.packageName
         return when {
-            packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
-                packageName, permissionName
-            )
-            packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
-                packageName, permissionName
-            )
+            packageState.isVendor ->
+                permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+            packageState.isProduct ->
+                permissionAllowlist.getProductPrivilegedAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
             packageState.isSystemExt ->
                 permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
-                    packageName, permissionName
+                    packageName,
+                    permissionName
                 )
             apexModuleName != null -> {
-                val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
-                    packageName, permissionName
-                )
+                val nonApexAllowlistState =
+                    permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
                 if (nonApexAllowlistState != null) {
                     // TODO(andreionea): Remove check as soon as all apk-in-apex
                     // permission allowlists are migrated.
                     Slog.w(
-                        LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
+                        LOG_TAG,
+                        "Package $packageName is an APK in APEX but has permission" +
                             " allowlist on the system image, please bundle the allowlist in the" +
                             " $apexModuleName APEX instead"
                     )
                 }
-                val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
-                    apexModuleName, packageName, permissionName
-                )
+                val apexAllowlistState =
+                    permissionAllowlist.getApexPrivilegedAppAllowlistState(
+                        apexModuleName,
+                        packageName,
+                        permissionName
+                    )
                 apexAllowlistState ?: nonApexAllowlistState
             }
             else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
@@ -1208,18 +1354,19 @@
         val knownPackages = newState.externalState.knownPackages
         val packageName = packageState.packageName
         if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
-            val shouldGrant = if (packageState.isUpdatedSystemApp) {
-                // For updated system applications, a privileged/oem permission
-                // is granted only if it had been defined by the original application.
-                val disabledSystemPackageState = newState.externalState
-                    .disabledSystemPackageStates[packageState.packageName]
-                val disabledSystemPackage = disabledSystemPackageState?.androidPackage
-                disabledSystemPackage != null &&
-                    permission.name in disabledSystemPackage.requestedPermissions &&
-                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
-            } else {
-                shouldGrantPrivilegedOrOemPermission(packageState, permission)
-            }
+            val shouldGrant =
+                if (packageState.isUpdatedSystemApp) {
+                    // For updated system applications, a privileged/oem permission
+                    // is granted only if it had been defined by the original application.
+                    val disabledSystemPackageState =
+                        newState.externalState.disabledSystemPackageStates[packageState.packageName]
+                    val disabledSystemPackage = disabledSystemPackageState?.androidPackage
+                    disabledSystemPackage != null &&
+                        permission.name in disabledSystemPackage.requestedPermissions &&
+                        shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
+                } else {
+                    shouldGrantPrivilegedOrOemPermission(packageState, permission)
+                }
             if (shouldGrant) {
                 return true
             }
@@ -1230,16 +1377,18 @@
             // we still want to blindly grant it to old apps.
             return true
         }
-        if (permission.isInstaller && (
-            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! ||
-                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!
-        )) {
+        if (
+            permission.isInstaller &&
+                (packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! ||
+                    packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!)
+        ) {
             // If this permission is to be granted to the system installer and
             // this app is an installer or permission controller, then it gets the permission.
             return true
         }
-        if (permission.isVerifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) {
+        if (
+            permission.isVerifier && packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!
+        ) {
             // If this permission is to be granted to the system verifier and
             // this app is a verifier, then it gets the permission.
             return true
@@ -1248,53 +1397,67 @@
             // Any pre-installed system app is allowed to get this permission.
             return true
         }
-        if (permission.isKnownSigner &&
-            androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
+        if (
+            permission.isKnownSigner &&
+                androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)
+        ) {
             // If the permission is to be granted to a known signer then check if any of this
             // app's signing certificates are in the trusted certificate digest Set.
             return true
         }
-        if (permission.isSetup &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) {
+        if (
+            permission.isSetup && packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!
+        ) {
             // If this permission is to be granted to the system setup wizard and
             // this app is a setup wizard, then it gets the permission.
             return true
         }
-        if (permission.isSystemTextClassifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) {
+        if (
+            permission.isSystemTextClassifier &&
+                packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!
+        ) {
             // Special permissions for the system default text classifier.
             return true
         }
-        if (permission.isConfigurator &&
-            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) {
+        if (
+            permission.isConfigurator &&
+                packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!
+        ) {
             // Special permissions for the device configurator.
             return true
         }
-        if (permission.isIncidentReportApprover &&
-            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) {
+        if (
+            permission.isIncidentReportApprover &&
+                packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!
+        ) {
             // If this permission is to be granted to the incident report approver and
             // this app is the incident report approver, then it gets the permission.
             return true
         }
-        if (permission.isAppPredictor &&
-            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) {
+        if (
+            permission.isAppPredictor &&
+                packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!
+        ) {
             // Special permissions for the system app predictor.
             return true
         }
-        if (permission.isCompanion &&
-            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) {
+        if (
+            permission.isCompanion &&
+                packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!
+        ) {
             // Special permissions for the system companion device manager.
             return true
         }
-        if (permission.isRetailDemo &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) {
+        if (
+            permission.isRetailDemo &&
+                packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!
+        ) {
             // Special permission granted only to the OEM specified retail demo app.
             // Note that the original code was passing app ID as UID, so this behavior is kept
             // unchanged.
             return true
         }
-        if (permission.isRecents &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) {
+        if (permission.isRecents && packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) {
             // Special permission for the recents app.
             return true
         }
@@ -1319,9 +1482,10 @@
                     // flag.
                     if (packageState.isVendor && !permission.isVendorPrivileged) {
                         Slog.w(
-                            LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
-                            " vendor app $packageName because it isn't a vendorPrivileged" +
-                            " permission"
+                            LOG_TAG,
+                            "Permission $permissionName cannot be granted to privileged" +
+                                " vendor app $packageName because it isn't a vendorPrivileged" +
+                                " permission"
                         )
                         return false
                     }
@@ -1330,8 +1494,11 @@
             }
             permission.isOem -> {
                 if (packageState.isOem) {
-                    val allowlistState = newState.externalState.permissionAllowlist
-                        .getOemAppAllowlistState(packageName, permissionName)
+                    val allowlistState =
+                        newState.externalState.permissionAllowlist.getOemAppAllowlistState(
+                            packageName,
+                            permissionName
+                        )
                     checkNotNull(allowlistState) {
                         "OEM permission $permissionName requested by package" +
                             " $packageName must be explicitly declared granted or not"
@@ -1358,13 +1525,18 @@
             val appId = externalState.packageStates[packageName]?.appId ?: continue
             newState.userStates.forEachIndexed { _, userId, _ ->
                 evaluatePermissionState(
-                    appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null
+                    appId,
+                    userId,
+                    Manifest.permission.PACKAGE_USAGE_STATS,
+                    null
                 )
             }
         }
         if (!privilegedPermissionAllowlistViolations.isEmpty()) {
-            throw IllegalStateException("Signature|privileged permissions not in privileged" +
-                " permission allowlist: $privilegedPermissionAllowlistViolations")
+            throw IllegalStateException(
+                "Signature|privileged permissions not in privileged" +
+                    " permission allowlist: $privilegedPermissionAllowlistViolations"
+            )
         }
     }
 
@@ -1389,10 +1561,14 @@
 
     fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
         state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
-                _, permissionTreeName, permissionTree ->
-            if (permissionName.startsWith(permissionTreeName) &&
-                permissionName.length > permissionTreeName.length &&
-                permissionName[permissionTreeName.length] == '.') {
+            _,
+            permissionTreeName,
+            permissionTree ->
+            if (
+                permissionName.startsWith(permissionTreeName) &&
+                    permissionName.length > permissionTreeName.length &&
+                    permissionName[permissionTreeName.length] == '.'
+            ) {
                 permissionTree
             } else {
                 null
@@ -1403,15 +1579,11 @@
         newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
     }
 
-    /**
-     * returns all permission group definitions available in the system
-     */
+    /** returns all permission group definitions available in the system */
     fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> =
         state.systemState.permissionGroups
 
-    /**
-     * returns all permission definitions available in the system
-     */
+    /** returns all permission definitions available in the system */
     fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
         state.systemState.permissions
 
@@ -1430,11 +1602,8 @@
     fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
         state.userStates[userId]?.appIdPermissionFlags?.get(appId)
 
-    fun GetStateScope.getPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String
-    ): Int = getPermissionFlags(state, appId, userId, permissionName)
+    fun GetStateScope.getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+        getPermissionFlags(state, appId, userId, permissionName)
 
     private fun MutateStateScope.getOldStatePermissionFlags(
         appId: Int,
@@ -1465,8 +1634,10 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
-        val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId]
-            .getWithDefault(permissionName, 0)
+        val oldFlags =
+            newState.userStates[userId]!!
+                .appIdPermissionFlags[appId]
+                .getWithDefault(permissionName, 0)
         val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
         if (oldFlags == newFlags) {
             return false
@@ -1517,38 +1688,37 @@
         private const val PLATFORM_PACKAGE_NAME = "android"
 
         // A set of permissions that we don't want to revoke when they are no longer implicit.
-        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf(
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.ACTIVITY_RECOGNITION,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-        )
+        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.ACCESS_MEDIA_LOCATION,
+                Manifest.permission.ACTIVITY_RECOGNITION,
+                Manifest.permission.READ_MEDIA_AUDIO,
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.READ_MEDIA_VIDEO,
+            )
 
-        private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
-            Manifest.permission.BLUETOOTH_ADVERTISE,
-            Manifest.permission.BLUETOOTH_CONNECT,
-            Manifest.permission.BLUETOOTH_SCAN,
-            Manifest.permission.NEARBY_WIFI_DEVICES
-        )
+        private val NEARBY_DEVICES_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.BLUETOOTH_ADVERTISE,
+                Manifest.permission.BLUETOOTH_CONNECT,
+                Manifest.permission.BLUETOOTH_SCAN,
+                Manifest.permission.NEARBY_WIFI_DEVICES
+            )
 
-        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
-            Manifest.permission.POST_NOTIFICATIONS
-        )
+        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(Manifest.permission.POST_NOTIFICATIONS)
 
-        private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
-        )
+        private val STORAGE_AND_MEDIA_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                Manifest.permission.READ_MEDIA_AUDIO,
+                Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.ACCESS_MEDIA_LOCATION,
+                Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+            )
 
-        /**
-         * Mask for all permission flags that can be set by the user
-         */
+        /** Mask for all permission flags that can be set by the user */
         private const val USER_SETTABLE_MASK =
             PermissionFlags.USER_SET or
                 PermissionFlags.USER_FIXED or
@@ -1558,16 +1728,14 @@
                 PermissionFlags.USER_SELECTED
 
         /**
-         * Mask for all permission flags that imply we shouldn't automatically modify the
-         * permission grant state.
+         * Mask for all permission flags that imply we shouldn't automatically modify the permission
+         * grant state.
          */
         private const val SYSTEM_OR_POLICY_FIXED_MASK =
             PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
     }
 
-    /**
-     * Listener for permission flags changes.
-     */
+    /** Listener for permission flags changes. */
     abstract class OnPermissionFlagsChangedListener {
         /**
          * Called when a permission flags change has been made to the upcoming new state.
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index b644d8f..edacda0 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -32,7 +32,6 @@
      * Upgrade the package permissions, if needed.
      *
      * @param version package version
-     *
      * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion]
      */
     fun MutateStateScope.upgradePackageState(
@@ -43,7 +42,8 @@
         val packageName = packageState.packageName
         if (version <= 3) {
             Slog.v(
-                LOG_TAG, "Allowlisting and upgrading background location permission for " +
+                LOG_TAG,
+                "Allowlisting and upgrading background location permission for " +
                     "package: $packageName, version: $version, user:$userId"
             )
             allowlistRestrictedPermissions(packageState, userId)
@@ -51,7 +51,8 @@
         }
         if (version <= 10) {
             Slog.v(
-                LOG_TAG, "Upgrading access media location permission for package: $packageName" +
+                LOG_TAG,
+                "Upgrading access media location permission for package: $packageName" +
                     ", version: $version, user: $userId"
             )
             upgradeAccessMediaLocationPermission(packageState, userId)
@@ -59,7 +60,8 @@
         // TODO Enable isAtLeastT check, when moving subsystem to mainline.
         if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) {
             Slog.v(
-                LOG_TAG, "Upgrading scoped permissions for package: $packageName" +
+                LOG_TAG,
+                "Upgrading scoped permissions for package: $packageName" +
                     ", version: $version, user: $userId"
             )
             upgradeAuralVisualMediaPermissions(packageState, userId)
@@ -67,7 +69,8 @@
         // TODO Enable isAtLeastU check, when moving subsystem to mainline.
         if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) {
             Slog.v(
-                LOG_TAG, "Upgrading visual media permission for package: $packageName" +
+                LOG_TAG,
+                "Upgrading visual media permission for package: $packageName" +
                     ", version: $version, user: $userId"
             )
             upgradeUserSelectedVisualMediaPermission(packageState, userId)
@@ -84,8 +87,11 @@
             if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) {
                 with(policy) {
                     updatePermissionFlags(
-                        packageState.appId, userId, permissionName,
-                        PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT
+                        packageState.appId,
+                        userId,
+                        permissionName,
+                        PermissionFlags.UPGRADE_EXEMPT,
+                        PermissionFlags.UPGRADE_EXEMPT
                     )
                 }
             }
@@ -96,21 +102,27 @@
         packageState: PackageState,
         userId: Int
     ) {
-        if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in
-            packageState.androidPackage!!.requestedPermissions) {
+        if (
+            Manifest.permission.ACCESS_BACKGROUND_LOCATION in
+                packageState.androidPackage!!.requestedPermissions
+        ) {
             val appId = packageState.appId
-            val accessFineLocationFlags = with(policy) {
-                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION)
-            }
-            val accessCoarseLocationFlags = with(policy) {
-                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION)
-            }
+            val accessFineLocationFlags =
+                with(policy) {
+                    getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION)
+                }
+            val accessCoarseLocationFlags =
+                with(policy) {
+                    getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION)
+                }
             val isForegroundLocationGranted =
                 PermissionFlags.isAppOpGranted(accessFineLocationFlags) ||
                     PermissionFlags.isAppOpGranted(accessCoarseLocationFlags)
             if (isForegroundLocationGranted) {
                 grantRuntimePermission(
-                    packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                    packageState,
+                    userId,
+                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                 )
             }
         }
@@ -120,24 +132,29 @@
         packageState: PackageState,
         userId: Int
     ) {
-        if (Manifest.permission.ACCESS_MEDIA_LOCATION in
-            packageState.androidPackage!!.requestedPermissions) {
-            val flags = with(policy) {
-                getPermissionFlags(
-                    packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE
-                )
-            }
+        if (
+            Manifest.permission.ACCESS_MEDIA_LOCATION in
+                packageState.androidPackage!!.requestedPermissions
+        ) {
+            val flags =
+                with(policy) {
+                    getPermissionFlags(
+                        packageState.appId,
+                        userId,
+                        Manifest.permission.READ_EXTERNAL_STORAGE
+                    )
+                }
             if (PermissionFlags.isAppOpGranted(flags)) {
                 grantRuntimePermission(
-                    packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION
+                    packageState,
+                    userId,
+                    Manifest.permission.ACCESS_MEDIA_LOCATION
                 )
             }
         }
     }
 
-    /**
-     * Upgrade permissions based on storage permissions grant
-     */
+    /** Upgrade permissions based on storage permissions grant */
     private fun MutateStateScope.upgradeAuralVisualMediaPermissions(
         packageState: PackageState,
         userId: Int
@@ -147,15 +164,15 @@
             return
         }
         val requestedPermissionNames = androidPackage.requestedPermissions
-        val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName ->
-            if (permissionName !in requestedPermissionNames) {
-                return@anyIndexed false
+        val isStorageUserGranted =
+            STORAGE_PERMISSIONS.anyIndexed { _, permissionName ->
+                if (permissionName !in requestedPermissionNames) {
+                    return@anyIndexed false
+                }
+                val flags =
+                    with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+                PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
             }
-            val flags = with(policy) {
-                getPermissionFlags(packageState.appId, userId, permissionName)
-            }
-            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
-        }
         if (isStorageUserGranted) {
             AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName ->
                 if (permissionName in requestedPermissionNames) {
@@ -165,9 +182,7 @@
         }
     }
 
-    /**
-     * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL]
-     */
+    /** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */
     private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission(
         packageState: PackageState,
         userId: Int
@@ -177,19 +192,21 @@
             return
         }
         val requestedPermissionNames = androidPackage.requestedPermissions
-        val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName ->
-            if (permissionName !in requestedPermissionNames) {
-                return@anyIndexed false
+        val isVisualMediaUserGranted =
+            VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName ->
+                if (permissionName !in requestedPermissionNames) {
+                    return@anyIndexed false
+                }
+                val flags =
+                    with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+                PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
             }
-            val flags = with(policy) {
-                getPermissionFlags(packageState.appId, userId, permissionName)
-            }
-            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
-        }
         if (isVisualMediaUserGranted) {
             if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) {
                 grantRuntimePermission(
-                    packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+                    packageState,
+                    userId,
+                    Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
                 )
             }
         }
@@ -201,7 +218,8 @@
         permissionName: String
     ) {
         Slog.v(
-            LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " +
+            LOG_TAG,
+            "Granting runtime permission for package: ${packageState.packageName}, " +
                 "permission: $permissionName, userId: $userId"
         )
         val permission = newState.systemState.permissions[permissionName]!!
@@ -220,13 +238,13 @@
         }
 
         flags = flags or PermissionFlags.RUNTIME_GRANTED
-        flags = flags andInv (
-            PermissionFlags.APP_OP_REVOKED or
-            PermissionFlags.IMPLICIT or
-            PermissionFlags.LEGACY_GRANTED or
-            PermissionFlags.HIBERNATION or
-            PermissionFlags.ONE_TIME
-        )
+        flags =
+            flags andInv
+                (PermissionFlags.APP_OP_REVOKED or
+                    PermissionFlags.IMPLICIT or
+                    PermissionFlags.LEGACY_GRANTED or
+                    PermissionFlags.HIBERNATION or
+                    PermissionFlags.ONE_TIME)
         with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
     }
 
@@ -234,39 +252,45 @@
         private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName
 
         private const val MASK_ANY_FIXED =
-            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or
-            PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED
+            PermissionFlags.USER_SET or
+                PermissionFlags.USER_FIXED or
+                PermissionFlags.POLICY_FIXED or
+                PermissionFlags.SYSTEM_FIXED
 
-        private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf(
-            Manifest.permission.ACCESS_BACKGROUND_LOCATION,
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.SEND_SMS,
-            Manifest.permission.RECEIVE_SMS,
-            Manifest.permission.RECEIVE_WAP_PUSH,
-            Manifest.permission.RECEIVE_MMS,
-            Manifest.permission.READ_CELL_BROADCASTS,
-            Manifest.permission.READ_CALL_LOG,
-            Manifest.permission.WRITE_CALL_LOG,
-            Manifest.permission.PROCESS_OUTGOING_CALLS
-        )
+        private val LEGACY_RESTRICTED_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                Manifest.permission.SEND_SMS,
+                Manifest.permission.RECEIVE_SMS,
+                Manifest.permission.RECEIVE_WAP_PUSH,
+                Manifest.permission.RECEIVE_MMS,
+                Manifest.permission.READ_CELL_BROADCASTS,
+                Manifest.permission.READ_CALL_LOG,
+                Manifest.permission.WRITE_CALL_LOG,
+                Manifest.permission.PROCESS_OUTGOING_CALLS
+            )
 
-        private val STORAGE_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE
-        )
-        private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
-        )
+        private val STORAGE_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE
+            )
+        private val AURAL_VISUAL_MEDIA_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_MEDIA_AUDIO,
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.ACCESS_MEDIA_LOCATION,
+                Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+            )
         // Visual media permissions in T
-        private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.ACCESS_MEDIA_LOCATION
-        )
+        private val VISUAL_MEDIA_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.ACCESS_MEDIA_LOCATION
+            )
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
index 37a4a90..1bee356 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
@@ -135,9 +135,7 @@
     ) {
         tag(TAG_DEVICE) {
             attributeInterned(ATTR_ID, deviceId)
-            permissionFlags.forEachIndexed { _, name, flags ->
-                serializePermission(name, flags)
-            }
+            permissionFlags.forEachIndexed { _, name, flags -> serializePermission(name, flags) }
         }
     }
 
@@ -145,11 +143,12 @@
         tag(TAG_PERMISSION) {
             attributeInterned(ATTR_NAME, name)
             // Never serialize one-time permissions as granted.
-            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
-                flags andInv PermissionFlags.RUNTIME_GRANTED
-            } else {
-                flags
-            }
+            val serializedFlags =
+                if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+                    flags andInv PermissionFlags.RUNTIME_GRANTED
+                } else {
+                    flags
+                }
             attributeInt(ATTR_FLAGS, serializedFlags)
         }
     }
@@ -166,4 +165,4 @@
         private const val ATTR_NAME = "name"
         private const val ATTR_FLAGS = "flags"
     }
-}
\ No newline at end of file
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 240585c..15a5859 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
@@ -53,8 +53,8 @@
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             if (appId in userState.appIdDevicePermissionFlags) {
-                newState.mutateUserStateAt(userStateIndex)
-                    .mutateAppIdDevicePermissionFlags() -= appId
+                newState.mutateUserStateAt(userStateIndex).mutateAppIdDevicePermissionFlags() -=
+                    appId
             }
         }
     }
@@ -85,10 +85,10 @@
         appId: Int,
         userId: Int
     ) {
-        resetPermissionStates(packageName, userId)
+        resetRuntimePermissions(packageName, userId)
     }
 
-    private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) {
+    fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) {
         // It's okay to skip resetting permissions for packages that are removed,
         // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
         val packageState = newState.externalState.packageStates[packageName] ?: return
@@ -96,10 +96,11 @@
         val appId = packageState.appId
         val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
         androidPackage.requestedPermissions.forEach { permissionName ->
-            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
-                it.packageName != packageName &&
-                    permissionName in it.androidPackage!!.requestedPermissions
-            }
+            val isRequestedByOtherPackages =
+                anyPackageInAppId(appId) {
+                    it.packageName != packageName &&
+                        permissionName in it.androidPackage!!.requestedPermissions
+                }
             if (isRequestedByOtherPackages) {
                 return@forEach
             }
@@ -116,7 +117,9 @@
         }
         newState.userStates.forEachIndexed { _, userId, userState ->
             userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed {
-                    _, deviceId, permissionFlags ->
+                _,
+                deviceId,
+                permissionFlags ->
                 permissionFlags.forEachReversedIndexed { _, permissionName, _ ->
                     if (permissionName !in requestedPermissions) {
                         setPermissionFlags(appId, deviceId, userId, permissionName, 0)
@@ -166,11 +169,17 @@
         userId: Int,
         permissionName: String
     ): Int {
-        val flags = state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId)
-                ?.getWithDefault(permissionName, 0) ?: 0
+        val flags =
+            state.userStates[userId]
+                ?.appIdDevicePermissionFlags
+                ?.get(appId)
+                ?.get(deviceId)
+                ?.getWithDefault(permissionName, 0)
+                ?: 0
         if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) {
             Slog.i(
-                LOG_TAG, "getPermissionFlags: appId=$appId, userId=$userId," +
+                LOG_TAG,
+                "getPermissionFlags: appId=$appId, userId=$userId," +
                     " deviceId=$deviceId, permissionName=$permissionName," +
                     " flags=${PermissionFlags.toString(flags)}"
             )
@@ -186,7 +195,12 @@
         flags: Int
     ): Boolean =
         updatePermissionFlags(
-            appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags
+            appId,
+            deviceId,
+            userId,
+            permissionName,
+            PermissionFlags.MASK_ALL,
+            flags
         )
 
     private fun MutateStateScope.updatePermissionFlags(
@@ -201,20 +215,23 @@
             Slog.w(LOG_TAG, "$permissionName is not a device aware permission.")
             return false
         }
-        val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId]
-            ?.get(deviceId).getWithDefault(permissionName, 0)
+        val oldFlags =
+            newState.userStates[userId]!!
+                .appIdDevicePermissionFlags[appId]
+                ?.get(deviceId)
+                .getWithDefault(permissionName, 0)
         val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
         if (oldFlags == newFlags) {
             return false
         }
         val appIdDevicePermissionFlags =
             newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags()
-        val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) {
-            MutableIndexedReferenceMap()
-        }
+        val devicePermissionFlags =
+            appIdDevicePermissionFlags.mutateOrPut(appId) { MutableIndexedReferenceMap() }
         if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) {
             Slog.i(
-                LOG_TAG, "setPermissionFlags(): appId=$appId, userId=$userId," +
+                LOG_TAG,
+                "setPermissionFlags(): appId=$appId, userId=$userId," +
                     " deviceId=$deviceId, permissionName=$permissionName," +
                     " newFlags=${PermissionFlags.toString(newFlags)}"
             )
@@ -229,40 +246,39 @@
         }
         listeners.forEachIndexed { _, it ->
             it.onDevicePermissionFlagsChanged(
-                appId, userId, deviceId, permissionName, oldFlags, newFlags
+                appId,
+                userId,
+                deviceId,
+                permissionName,
+                oldFlags,
+                newFlags
             )
         }
         return true
     }
 
     fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) {
-            listeners = listeners + listener
-        }
+        synchronized(listenersLock) { listeners = listeners + listener }
     }
 
     fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) {
-            listeners = listeners - listener
-        }
+        synchronized(listenersLock) { listeners = listeners - listener }
     }
 
     private fun isDeviceAwarePermission(permissionName: String): Boolean =
-            DEVICE_AWARE_PERMISSIONS.contains(permissionName)
+        DEVICE_AWARE_PERMISSIONS.contains(permissionName)
 
     companion object {
         private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName
 
-        /**
-         * These permissions are supported for virtual devices.
-         */
+        /** These permissions are supported for virtual devices. */
         // TODO: b/298661870 - Use new API to get the list of device aware permissions.
         val DEVICE_AWARE_PERMISSIONS = emptySet<String>()
     }
 
     /**
-     * TODO: b/289355341 - implement listener for permission changes
-     * Listener for permission flags changes.
+     * TODO: b/289355341 - implement listener for permission changes Listener for permission flags
+     *   changes.
      */
     abstract class OnDevicePermissionFlagsChangedListener {
         /**
@@ -288,4 +304,4 @@
          */
         abstract fun onStateMutated()
     }
-}
\ No newline at end of file
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index c7fe1a9..aa56928 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -26,8 +26,7 @@
     val isReconciled: Boolean,
     val type: Int,
     val appId: Int,
-    @Suppress("ArrayInDataClass")
-    val gids: IntArray = EmptyArray.INT,
+    @Suppress("ArrayInDataClass") val gids: IntArray = EmptyArray.INT,
     val areGidsPerUser: Boolean = false
 ) {
     inline val name: String
@@ -43,8 +42,7 @@
         get() = type == TYPE_DYNAMIC
 
     inline val protectionLevel: Int
-        @Suppress("DEPRECATION")
-        get() = permissionInfo.protectionLevel
+        @Suppress("DEPRECATION") get() = permissionInfo.protectionLevel
 
     inline val protection: Int
         get() = permissionInfo.protection
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 550d148..b9d89c2 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -32,15 +32,12 @@
  *
  * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so
  * that:
- *
  * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package
  *   `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing
  *   edge cases during module rollbacks.
- *
  * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and
  *   implicit permissions split from non-runtime permissions are never revoked, without checking
  *   split permissions and package state everywhere slowly and in slightly different ways.
- *
  * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and
  *   potentially re-granting permissions upon restriction state changes.
  *
@@ -55,9 +52,7 @@
  * don't have any effect on the binary permission state.
  */
 object PermissionFlags {
-    /**
-     * Permission flag for a normal permission that is granted at package installation.
-     */
+    /** Permission flag for a normal permission that is granted at package installation. */
     const val INSTALL_GRANTED = 1 shl 0
 
     /**
@@ -97,8 +92,8 @@
     /**
      * Permission flag for a runtime permission whose state is set by the user.
      *
-     * For example, this flag may be set when the permission is allowed by the user in the
-     * request permission dialog, or managed in the permission settings.
+     * For example, this flag may be set when the permission is allowed by the user in the request
+     * permission dialog, or managed in the permission settings.
      *
      * @see PackageManager.FLAG_PERMISSION_USER_SET
      */
@@ -290,8 +285,8 @@
     /**
      * Permission flag for a runtime permission that is selected by the user.
      *
-     * For example, this flag may be set when one of the coarse/fine location accuracies is
-     * selected by the user.
+     * For example, this flag may be set when one of the coarse/fine location accuracies is selected
+     * by the user.
      *
      * This flag is informational and managed by PermissionController.
      *
@@ -299,28 +294,37 @@
      */
     const val USER_SELECTED = 1 shl 23
 
-    /**
-     * Mask for all permission flags.
-     */
+    /** Mask for all permission flags. */
     const val MASK_ALL = 0.inv()
 
-    /**
-     * Mask for all permission flags that may be applied to a runtime permission.
-     */
-    const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or
-        SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or
-        USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or
-        SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or
-        APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED
+    /** Mask for all permission flags that may be applied to a runtime permission. */
+    const val MASK_RUNTIME =
+        ROLE or
+            RUNTIME_GRANTED or
+            USER_SET or
+            USER_FIXED or
+            POLICY_FIXED or
+            SYSTEM_FIXED or
+            PREGRANT or
+            LEGACY_GRANTED or
+            IMPLICIT_GRANTED or
+            IMPLICIT or
+            USER_SENSITIVE_WHEN_GRANTED or
+            USER_SENSITIVE_WHEN_REVOKED or
+            INSTALLER_EXEMPT or
+            SYSTEM_EXEMPT or
+            UPGRADE_EXEMPT or
+            RESTRICTION_REVOKED or
+            SOFT_RESTRICTED or
+            APP_OP_REVOKED or
+            ONE_TIME or
+            HIBERNATION or
+            USER_SELECTED
 
-    /**
-     * Mask for all permission flags about permission exemption.
-     */
+    /** Mask for all permission flags about permission exemption. */
     const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT
 
-    /**
-     * Mask for all permission flags about permission restriction.
-     */
+    /** Mask for all permission flags about permission restriction. */
     const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED
 
     fun isPermissionGranted(flags: Int): Boolean {
@@ -363,11 +367,13 @@
             apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
         }
         if (flags.hasBits(IMPLICIT)) {
-            apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) {
-                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-            } else {
-                PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
-            }
+            apiFlags =
+                apiFlags or
+                    if (flags.hasBits(LEGACY_GRANTED)) {
+                        PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                    } else {
+                        PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+                    }
         }
         if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
             apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
@@ -440,8 +446,10 @@
         }
         flags = flags or (oldFlags and LEGACY_GRANTED)
         flags = flags or (oldFlags and IMPLICIT_GRANTED)
-        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
-            apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) {
+        if (
+            apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
+                apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
+        ) {
             flags = flags or IMPLICIT
         }
         if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) {
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 bb24d51..ab3d78c 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
@@ -41,10 +41,10 @@
 import android.os.ServiceManager
 import android.os.UserHandle
 import android.os.UserManager
-import android.permission.flags.Flags
 import android.permission.IOnPermissionsChangeListener
 import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
+import android.permission.flags.Flags
 import android.provider.Settings
 import android.util.ArrayMap
 import android.util.ArraySet
@@ -88,28 +88,25 @@
 import com.android.server.pm.UserManagerService
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils
 import com.android.server.pm.permission.LegacyPermission
-import com.android.server.pm.permission.Permission as LegacyPermission2
 import com.android.server.pm.permission.LegacyPermissionSettings
 import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.Permission as LegacyPermission2
 import com.android.server.pm.permission.PermissionManagerServiceInterface
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
 import com.android.server.policy.SoftRestrictedPermissionPolicy
-import libcore.util.EmptyArray
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.TimeoutException
+import libcore.util.EmptyArray
 
-/**
- * Modern implementation of [PermissionManagerServiceInterface].
- */
-class PermissionService(
-    private val service: AccessCheckingService
-) : PermissionManagerServiceInterface {
+/** Modern implementation of [PermissionManagerServiceInterface]. */
+class PermissionService(private val service: AccessCheckingService) :
+    PermissionManagerServiceInterface {
     private val policy =
         service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
 
@@ -131,8 +128,7 @@
     private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
 
     private val storageVolumeLock = Any()
-    @GuardedBy("storageVolumeLock")
-    private val mountedStorageVolumes = ArraySet<String?>()
+    @GuardedBy("storageVolumeLock") private val mountedStorageVolumes = ArraySet<String?>()
     @GuardedBy("storageVolumeLock")
     private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>()
 
@@ -144,8 +140,8 @@
      * A permission backup might contain apps that are not installed. In this case we delay the
      * restoration until the app is installed.
      *
-     * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where
-     * there is **no more** delayed backup left.
+     * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where there is **no
+     * more** delayed backup left.
      */
     private val isDelayedPermissionBackupFinished = SparseBooleanArray()
 
@@ -154,9 +150,10 @@
         packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
         packageManagerLocal =
             LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
-        platformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)
-        )
+        platformCompat =
+            IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)
+            )
         systemConfig = SystemConfig.getInstance()
         userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
         userManagerService = UserManagerService.getInstance()
@@ -166,8 +163,8 @@
         PackageManager.invalidatePackageInfoCache()
         PermissionManager.disablePackageNamePermissionCache()
 
-        handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true)
-            .apply { start() }
+        handlerThread =
+            ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true).apply { start() }
         handler = Handler(handlerThread.looper)
         onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper)
         onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener()
@@ -181,9 +178,7 @@
                 return emptyList()
             }
 
-            val permissionGroups = service.getState {
-                with(policy) { getPermissionGroups() }
-            }
+            val permissionGroups = service.getState { with(policy) { getPermissionGroups() } }
 
             return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup ->
                 if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
@@ -206,9 +201,9 @@
                 return null
             }
 
-            permissionGroup = service.getState {
-                with(policy) { getPermissionGroups()[permissionGroupName] }
-            } ?: return null
+            permissionGroup =
+                service.getState { with(policy) { getPermissionGroups()[permissionGroupName] } }
+                    ?: return null
 
             if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
                 return null
@@ -242,29 +237,28 @@
                 return null
             }
 
-            permission = service.getState {
-                with(policy) { getPermissions()[permissionName] }
-            } ?: return null
+            permission =
+                service.getState { with(policy) { getPermissions()[permissionName] } }
+                    ?: return null
 
             if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) {
                 return null
             }
 
             val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage
-            targetSdkVersion = when {
-                // System sees all flags.
-                isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
-                opPackage != null -> opPackage.targetSdkVersion
-                else -> Build.VERSION_CODES.CUR_DEVELOPMENT
-            }
+            targetSdkVersion =
+                when {
+                    // System sees all flags.
+                    isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
+                    opPackage != null -> opPackage.targetSdkVersion
+                    else -> Build.VERSION_CODES.CUR_DEVELOPMENT
+                }
         }
 
         return permission.generatePermissionInfo(flags, targetSdkVersion)
     }
 
-    /**
-     * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly.
-     */
+    /** Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. */
     private fun Permission.generatePermissionInfo(
         flags: Int,
         targetSdkVersion: Int = Build.VERSION_CODES.CUR_DEVELOPMENT
@@ -296,22 +290,27 @@
                 return null
             }
 
-            val permissions = service.getState {
-                if (permissionGroupName != null) {
-                    val permissionGroup =
-                        with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null
+            val permissions =
+                service.getState {
+                    if (permissionGroupName != null) {
+                        val permissionGroup =
+                            with(policy) { getPermissionGroups()[permissionGroupName] }
+                                ?: return null
 
-                    if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
-                        return null
+                        if (
+                            !snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
+                        ) {
+                            return null
+                        }
                     }
+
+                    with(policy) { getPermissions() }
                 }
 
-                with(policy) { getPermissions() }
-            }
-
             return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
-                if (permission.groupName == permissionGroupName &&
-                    snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+                if (
+                    permission.groupName == permissionGroupName &&
+                        snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
                 ) {
                     permission.generatePermissionInfo(flags)
                 } else {
@@ -334,9 +333,7 @@
     private inline fun getPermissionsWithProtectionOrProtectionFlags(
         predicate: (Permission) -> Boolean
     ): List<PermissionInfo> {
-        val permissions = service.getState {
-            with(policy) { getPermissions() }
-        }
+        val permissions = service.getState { with(policy) { getPermissions() } }
 
         return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
             if (predicate(permission)) {
@@ -348,18 +345,16 @@
     }
 
     override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
-        val permission = service.getState {
-            with(policy) { getPermissions()[permissionName] }
-        } ?: return EmptyArray.INT
+        val permission =
+            service.getState { with(policy) { getPermissions()[permissionName] } }
+                ?: return EmptyArray.INT
         return permission.getGidsForUser(userId)
     }
 
     override fun getInstalledPermissions(packageName: String): Set<String> {
         requireNotNull(packageName) { "packageName cannot be null" }
 
-        val permissions = service.getState {
-            with(policy) { getPermissions() }
-        }
+        val permissions = service.getState { with(policy) { getPermissions() } }
 
         return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission ->
             if (permission.packageName == packageName) {
@@ -398,9 +393,8 @@
             permissionInfo.protectionLevel =
                 PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel)
 
-            val newPermission = Permission(
-                permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId
-            )
+            val newPermission =
+                Permission(permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId)
 
             with(policy) { addPermission(newPermission, !async) }
         }
@@ -431,7 +425,7 @@
         val callingUid = Binder.getCallingUid()
         val permissionTree = with(policy) { findPermissionTree(permissionName) }
         if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) {
-                return permissionTree
+            return permissionTree
         }
 
         throw SecurityException(
@@ -447,8 +441,9 @@
         // if that plus the size of 'info' would exceed our stated maximum.
         if (permissionTree.appId != Process.SYSTEM_UID) {
             val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree)
-            if (permissionTreeFootprint + permissionInfo.calculateFootprint() >
-                MAX_PERMISSION_TREE_FOOTPRINT
+            if (
+                permissionTreeFootprint + permissionInfo.calculateFootprint() >
+                    MAX_PERMISSION_TREE_FOOTPRINT
             ) {
                 throw SecurityException("Permission tree size cap exceeded")
             }
@@ -483,14 +478,16 @@
                 packageManagerInternal.getPackageStateInternal(androidPackage.packageName)
             if (packageState == null) {
                 Slog.e(
-                    LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" +
+                    LOG_TAG,
+                    "checkUidPermission: PackageState not found for AndroidPackage" +
                         " $androidPackage"
                 )
                 return PackageManager.PERMISSION_DENIED
             }
-            val isPermissionGranted = service.getState {
-                isPermissionGranted(packageState, userId, permissionName, deviceId)
-            }
+            val isPermissionGranted =
+                service.getState {
+                    isPermissionGranted(packageState, userId, permissionName, deviceId)
+                }
             return if (isPermissionGranted) {
                 PackageManager.PERMISSION_GRANTED
             } else {
@@ -505,9 +502,7 @@
         }
     }
 
-    /**
-     * Internal implementation that should only be called by [checkUidPermission].
-     */
+    /** Internal implementation that should only be called by [checkUidPermission]. */
     private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean {
         val uidPermissions = systemConfig.systemPermissions[uid] ?: return false
         if (permissionName in uidPermissions) {
@@ -532,12 +527,14 @@
             return PackageManager.PERMISSION_DENIED
         }
 
-        val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
-            .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return PackageManager.PERMISSION_DENIED
 
-        val isPermissionGranted = service.getState {
-            isPermissionGranted(packageState, userId, permissionName, deviceId)
-        }
+        val isPermissionGranted =
+            service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) }
         return if (isPermissionGranted) {
             PackageManager.PERMISSION_GRANTED
         } else {
@@ -566,8 +563,15 @@
         }
 
         val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
-        if (fullerPermissionName != null &&
-            isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId)
+        if (
+            fullerPermissionName != null &&
+                isSinglePermissionGranted(
+                    appId,
+                    userId,
+                    isInstantApp,
+                    fullerPermissionName,
+                    deviceId
+                )
         ) {
             return true
         }
@@ -575,9 +579,7 @@
         return false
     }
 
-    /**
-     * Internal implementation that should only be called by [isPermissionGranted].
-     */
+    /** Internal implementation that should only be called by [isPermissionGranted]. */
     private fun GetStateScope.isSinglePermissionGranted(
         appId: Int,
         userId: Int,
@@ -604,20 +606,27 @@
         requireNotNull(packageName) { "packageName cannot be null" }
         Preconditions.checkArgumentNonnegative(userId, "userId")
 
-        val packageState = packageManagerLocal.withUnfilteredSnapshot()
-            .use { it.getPackageState(packageName) }
+        val packageState =
+            packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) }
         if (packageState == null) {
             Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
             return emptySet()
         }
 
         service.getState {
-            val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) }
-                ?: return emptySet()
+            val permissionFlags =
+                with(policy) { getUidPermissionFlags(packageState.appId, userId) }
+                    ?: return emptySet()
 
             return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
-                if (isPermissionGranted(
-                        packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) {
+                if (
+                    isPermissionGranted(
+                        packageState,
+                        userId,
+                        permissionName,
+                        Context.DEVICE_ID_DEFAULT
+                    )
+                ) {
                     permissionName
                 } else {
                     null
@@ -635,8 +644,8 @@
             // permission state is not found, now we always return at least global GIDs. This is
             // more consistent with the pre-S-refactor behavior. This is also because we are now
             // actively trimming the per-UID objects when empty.
-            val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) }
-                ?: return globalGids.copyOf()
+            val permissionFlags =
+                with(policy) { getUidPermissionFlags(appId, userId) } ?: return globalGids.copyOf()
 
             val gids = GrowingIntArray.wrap(globalGids)
             permissionFlags.forEachIndexed { _, permissionName, flags ->
@@ -644,8 +653,8 @@
                     return@forEachIndexed
                 }
 
-                val permission = with(policy) { getPermissions()[permissionName] }
-                    ?: return@forEachIndexed
+                val permission =
+                    with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed
                 val permissionGids = permission.getGidsForUser(userId)
                 if (permissionGids.isEmpty()) {
                     return@forEachIndexed
@@ -662,9 +671,7 @@
         deviceId: Int,
         userId: Int
     ) {
-        setRuntimePermissionGranted(
-            packageName, userId, permissionName, deviceId, isGranted = true
-        )
+        setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true)
     }
 
     override fun revokeRuntimePermission(
@@ -675,7 +682,12 @@
         reason: String?
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason
+            packageName,
+            userId,
+            permissionName,
+            deviceId,
+            isGranted = false,
+            revokeReason = reason
         )
     }
 
@@ -684,8 +696,12 @@
         userId: Int
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT,
-            isGranted = false, skipKillUid = true
+            packageName,
+            userId,
+            Manifest.permission.POST_NOTIFICATIONS,
+            Context.DEVICE_ID_DEFAULT,
+            isGranted = false,
+            skipKillUid = true
         )
     }
 
@@ -704,19 +720,24 @@
     ) {
         val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission"
         val callingUid = Binder.getCallingUid()
-        val isDebugEnabled = if (isGranted) {
-            PermissionManager.DEBUG_TRACE_GRANTS
-        } else {
-            PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
-        }
-        if (isDebugEnabled &&
-            PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
+        val isDebugEnabled =
+            if (isGranted) {
+                PermissionManager.DEBUG_TRACE_GRANTS
+            } else {
+                PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
+            }
+        if (
+            isDebugEnabled &&
+                PermissionManager.shouldTraceGrant(packageName, permissionName, userId)
+        ) {
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
             Slog.i(
-                LOG_TAG, "$methodName(packageName = $packageName," +
+                LOG_TAG,
+                "$methodName(packageName = $packageName," +
                     " permissionName = $permissionName" +
                     (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
-                    ", userId = $userId," + " callingUid = $callingUidName ($callingUid))",
+                    ", userId = $userId," +
+                    " callingUid = $callingUidName ($callingUid))",
                 RuntimeException()
             )
         }
@@ -727,23 +748,31 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = true, methodName
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = true,
+            methodName
         )
-        val enforcedPermissionName = if (isGranted) {
-            Manifest.permission.GRANT_RUNTIME_PERMISSIONS
-        } else {
-            Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
-        }
+        val enforcedPermissionName =
+            if (isGranted) {
+                Manifest.permission.GRANT_RUNTIME_PERMISSIONS
+            } else {
+                Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+            }
         context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName)
 
         val packageState: PackageState?
-        val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames(
-            KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
-        ).first()
+        val permissionControllerPackageName =
+            packageManagerInternal
+                .getKnownPackageNames(
+                    KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
+                    UserHandle.USER_SYSTEM
+                )
+                .first()
         val permissionControllerPackageState: PackageState?
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
-            packageState = snapshot.filtered(callingUid, userId)
-                .use { it.getPackageState(packageName) }
+            packageState =
+                snapshot.filtered(callingUid, userId).use { it.getPackageState(packageName) }
             permissionControllerPackageState =
                 snapshot.getPackageState(permissionControllerPackageName)
         }
@@ -756,11 +785,13 @@
             return
         }
 
-        val canManageRolePermission = isRootOrSystemUid(callingUid) ||
-            UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
-        val overridePolicyFixed = context.checkCallingOrSelfPermission(
-            Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
-        ) == PackageManager.PERMISSION_GRANTED
+        val canManageRolePermission =
+            isRootOrSystemUid(callingUid) ||
+                UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
+        val overridePolicyFixed =
+            context.checkCallingOrSelfPermission(
+                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
+            ) == PackageManager.PERMISSION_GRANTED
 
         service.mutateState {
             with(onPermissionFlagsChangedListener) {
@@ -773,8 +804,15 @@
             }
 
             setRuntimePermissionGranted(
-                packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission,
-                overridePolicyFixed, reportError = true, methodName
+                packageState,
+                userId,
+                permissionName,
+                deviceId,
+                isGranted,
+                canManageRolePermission,
+                overridePolicyFixed,
+                reportError = true,
+                methodName
             )
         }
     }
@@ -791,8 +829,9 @@
                     PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {}
                     else -> {
                         Slog.w(
-                            LOG_TAG, "setRequestedPermissionStates: Unknown permission state" +
-                            " $permissionState for permission $permissionName"
+                            LOG_TAG,
+                            "setRequestedPermissionStates: Unknown permission state" +
+                                " $permissionState for permission $permissionName"
                         )
                         return@forEachIndexed
                     }
@@ -800,35 +839,50 @@
                 if (permissionName !in packageState.androidPackage!!.requestedPermissions) {
                     return@forEachIndexed
                 }
-                val permission = with(policy) { getPermissions()[permissionName] }
-                    ?: return@forEachIndexed
+                val permission =
+                    with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed
                 when {
                     permission.isDevelopment || permission.isRuntime -> {
-                        if (permissionState ==
-                            PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) {
+                        if (
+                            permissionState ==
+                                PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
+                        ) {
                             setRuntimePermissionGranted(
-                                packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT,
-                                isGranted = true, canManageRolePermission = false,
-                                overridePolicyFixed = false, reportError = false,
+                                packageState,
+                                userId,
+                                permissionName,
+                                Context.DEVICE_ID_DEFAULT,
+                                isGranted = true,
+                                canManageRolePermission = false,
+                                overridePolicyFixed = false,
+                                reportError = false,
                                 "setRequestedPermissionStates"
                             )
                             updatePermissionFlags(
-                                packageState.appId, userId, permissionName,
+                                packageState.appId,
+                                userId,
+                                permissionName,
                                 Context.DEVICE_ID_DEFAULT,
                                 PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or
-                                PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
+                                    PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                0,
                                 canUpdateSystemFlags = false,
                                 reportErrorForUnknownPermission = false,
-                                isPermissionRequested = true, "setRequestedPermissionStates",
+                                isPermissionRequested = true,
+                                "setRequestedPermissionStates",
                                 packageState.packageName
                             )
                         }
                     }
-                    permission.isAppOp && permissionName in
+                    permission.isAppOp &&
+                        permissionName in
                             PackageInstallerService.INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS ->
                         setAppOpPermissionGranted(
-                            packageState, userId, permissionName, permissionState ==
-                                    PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
+                            packageState,
+                            userId,
+                            permissionName,
+                            permissionState ==
+                                PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
                         )
                     else -> {}
                 }
@@ -836,9 +890,7 @@
         }
     }
 
-    /**
-     * Set whether a runtime permission is granted, without any validation on caller.
-     */
+    /** Set whether a runtime permission is granted, without any validation on caller. */
     private fun MutateStateScope.setRuntimePermissionGranted(
         packageState: PackageState,
         userId: Int,
@@ -876,8 +928,11 @@
                     // their permissions as always granted
                     return
                 }
-                if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp &&
-                    !permission.isInstant) {
+                if (
+                    isGranted &&
+                        packageState.getUserStateOrDefault(userId).isInstantApp &&
+                        !permission.isInstant
+                ) {
                     if (reportError) {
                         throw SecurityException(
                             "Cannot grant non-instant permission $permissionName to package" +
@@ -913,7 +968,8 @@
         if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
             if (reportError) {
                 Slog.e(
-                    LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" +
+                    LOG_TAG,
+                    "$methodName: Cannot change system fixed permission $permissionName" +
                         " for package $packageName"
                 )
             }
@@ -923,7 +979,8 @@
         if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) {
             if (reportError) {
                 Slog.e(
-                    LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" +
+                    LOG_TAG,
+                    "$methodName: Cannot change policy fixed permission $permissionName" +
                         " for package $packageName"
                 )
             }
@@ -933,7 +990,8 @@
         if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
             if (reportError) {
                 Slog.e(
-                    LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" +
+                    LOG_TAG,
+                    "$methodName: Cannot grant hard-restricted non-exempt permission" +
                         " $permissionName to package $packageName"
                 )
             }
@@ -942,14 +1000,19 @@
 
         if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
             // TODO: Refactor SoftRestrictedPermissionPolicy.
-            val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission(
-                context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
-                androidPackage, UserHandle.of(userId), permissionName
-            )
+            val softRestrictedPermissionPolicy =
+                SoftRestrictedPermissionPolicy.forPermission(
+                    context,
+                    AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
+                    androidPackage,
+                    UserHandle.of(userId),
+                    permissionName
+                )
             if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
                 if (reportError) {
                     Slog.e(
-                        LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" +
+                        LOG_TAG,
+                        "$methodName: Cannot grant soft-restricted non-exempt permission" +
                             " $permissionName to package $packageName"
                     )
                 }
@@ -965,15 +1028,17 @@
         setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
 
         if (permission.isRuntime) {
-            val action = if (isGranted) {
-                MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
-            } else {
-                MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
-            }
-            val log = LogMaker(action).apply {
-                setPackageName(packageName)
-                addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName)
-            }
+            val action =
+                if (isGranted) {
+                    MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
+                } else {
+                    MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
+                }
+            val log =
+                LogMaker(action).apply {
+                    setPackageName(packageName)
+                    addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName)
+                }
             metricsLogger.write(log)
         }
     }
@@ -984,8 +1049,8 @@
         permissionName: String,
         isGranted: Boolean
     ) {
-        val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as
-            AppIdAppOpPolicy
+        val appOpPolicy =
+            service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
         val appOpName = AppOpsManager.permissionToOp(permissionName)!!
         val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
         with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
@@ -1003,17 +1068,20 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
             "getPermissionFlags"
         )
         enforceCallingOrSelfAnyPermission(
-            "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            "getPermissionFlags",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
             Manifest.permission.GET_RUNTIME_PERMISSIONS
         )
 
-        val packageState = packageManagerLocal.withFilteredSnapshot()
-            .use { it.getPackageState(packageName) }
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) }
         if (packageState == null) {
             Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
             return 0
@@ -1045,12 +1113,17 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
             "isPermissionRevokedByPolicy"
         )
 
-        val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
-            .use { it.getPackageState(packageName) } ?: return false
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return false
 
         service.getState {
             if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
@@ -1069,12 +1142,13 @@
         // TODO(b/173235285): Some caller may pass USER_ALL as userId.
         // Preconditions.checkArgumentNonnegative(userId, "userId")
 
-        val packageState = packageManagerLocal.withUnfilteredSnapshot()
-            .use { it.getPackageState(packageName) } ?: return false
+        val packageState =
+            packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) }
+                ?: return false
 
-        val permissionFlags = service.getState {
-            with(policy) { getUidPermissionFlags(packageState.appId, userId) }
-        } ?: return false
+        val permissionFlags =
+            service.getState { with(policy) { getUidPermissionFlags(packageState.appId, userId) } }
+                ?: return false
         return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) }
     }
 
@@ -1090,13 +1164,18 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
             "shouldShowRequestPermissionRationale"
         )
 
         val callingUid = Binder.getCallingUid()
-        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
-            .use { it.getPackageState(packageName) } ?: return false
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(callingUid, userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return false
         val appId = packageState.appId
         if (UserHandle.getAppId(callingUid) != appId) {
             return false
@@ -1115,17 +1194,24 @@
         }
 
         if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) {
-            val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity {
-                try {
-                    platformCompat.isChangeEnabledByPackageName(
-                        BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId
-                    )
-                } catch (e: RemoteException) {
-                    Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
-                        " compatibility change is enabled", e)
-                    false
+            val isBackgroundRationaleChangeEnabled =
+                Binder::class.withClearedCallingIdentity {
+                    try {
+                        platformCompat.isChangeEnabledByPackageName(
+                            BACKGROUND_RATIONALE_CHANGE_ID,
+                            packageName,
+                            userId
+                        )
+                    } catch (e: RemoteException) {
+                        Slog.e(
+                            LOG_TAG,
+                            "shouldShowRequestPermissionRationale: Unable to check if" +
+                                " compatibility change is enabled",
+                            e
+                        )
+                        false
+                    }
                 }
-            }
             if (isBackgroundRationaleChangeEnabled) {
                 return true
             }
@@ -1144,20 +1230,30 @@
         userId: Int
     ) {
         val callingUid = Binder.getCallingUid()
-        if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES &&
-            PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
-            val flagMaskString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
-            )
-            val flagValuesString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
-            )
+        if (
+            PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES &&
+                PermissionManager.shouldTraceGrant(packageName, permissionName, userId)
+        ) {
+            val flagMaskString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagMask.toLong()
+                )
+            val flagValuesString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagValues.toLong()
+                )
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
             Slog.i(
-                LOG_TAG, "updatePermissionFlags(packageName = $packageName," +
+                LOG_TAG,
+                "updatePermissionFlags(packageName = $packageName," +
                     " permissionName = $permissionName, flagMask = $flagMaskString," +
                     " flagValues = $flagValuesString, userId = $userId," +
-                    " callingUid = $callingUidName ($callingUid))", RuntimeException()
+                    " callingUid = $callingUidName ($callingUid))",
+                RuntimeException()
             )
         }
 
@@ -1167,11 +1263,14 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = true,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = true,
             "updatePermissionFlags"
         )
         enforceCallingOrSelfAnyPermission(
-            "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            "updatePermissionFlags",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
         )
 
@@ -1208,8 +1307,10 @@
         // Different from the old implementation, which returns when package doesn't exist but
         // throws when package exists but isn't visible, we now return in both cases to avoid
         // leaking the package existence.
-        if (androidPackage == null ||
-            packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) {
+        if (
+            androidPackage == null ||
+                packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)
+        ) {
             Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
             return
         }
@@ -1219,26 +1320,35 @@
         // permissions.
         val canUpdateSystemFlags = isRootOrSystemUid(callingUid)
 
-        val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) {
-            // Fast path, the current package has requested the permission.
-            true
-        } else {
-            // Slow path, go through all shared user packages.
-            val sharedUserPackageNames =
-                packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId)
-            sharedUserPackageNames.any { sharedUserPackageName ->
-                val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName)
-                sharedUserPackage != null &&
-                    permissionName in sharedUserPackage.requestedPermissions
+        val isPermissionRequested =
+            if (permissionName in androidPackage.requestedPermissions) {
+                // Fast path, the current package has requested the permission.
+                true
+            } else {
+                // Slow path, go through all shared user packages.
+                val sharedUserPackageNames =
+                    packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId)
+                sharedUserPackageNames.any { sharedUserPackageName ->
+                    val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName)
+                    sharedUserPackage != null &&
+                        permissionName in sharedUserPackage.requestedPermissions
+                }
             }
-        }
 
         val appId = packageState.appId
         service.mutateState {
             updatePermissionFlags(
-                appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags,
-                reportErrorForUnknownPermission = true, isPermissionRequested,
-                "updatePermissionFlags", packageName
+                appId,
+                userId,
+                permissionName,
+                deviceId,
+                flagMask,
+                flagValues,
+                canUpdateSystemFlags,
+                reportErrorForUnknownPermission = true,
+                isPermissionRequested,
+                "updatePermissionFlags",
+                packageName
             )
         }
     }
@@ -1246,17 +1356,25 @@
     override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
         val callingUid = Binder.getCallingUid()
         if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) {
-            val flagMaskString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
-            )
-            val flagValuesString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
-            )
+            val flagMaskString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagMask.toLong()
+                )
+            val flagValuesString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagValues.toLong()
+                )
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
             Slog.i(
-                LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
+                LOG_TAG,
+                "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
                     " flagValues = $flagValuesString, userId = $userId," +
-                    " callingUid = $callingUidName ($callingUid))", RuntimeException()
+                    " callingUid = $callingUidName ($callingUid))",
+                RuntimeException()
             )
         }
 
@@ -1266,11 +1384,14 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = true,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = true,
             "updatePermissionFlagsForAllApps"
         )
         enforceCallingOrSelfAnyPermission(
-            "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            "updatePermissionFlagsForAllApps",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
         )
 
@@ -1278,26 +1399,30 @@
         // flag, we now properly sanitize all flags as in updatePermissionFlags().
         val canUpdateSystemFlags = isRootOrSystemUid(callingUid)
 
-        val packageStates = packageManagerLocal.withUnfilteredSnapshot()
-            .use { it.packageStates }
+        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
         service.mutateState {
             packageStates.forEach { (packageName, packageState) ->
                 val androidPackage = packageState.androidPackage ?: return@forEach
                 androidPackage.requestedPermissions.forEach { permissionName ->
                     updatePermissionFlags(
-                        packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT,
-                        flagMask, flagValues, canUpdateSystemFlags,
+                        packageState.appId,
+                        userId,
+                        permissionName,
+                        Context.DEVICE_ID_DEFAULT,
+                        flagMask,
+                        flagValues,
+                        canUpdateSystemFlags,
                         reportErrorForUnknownPermission = false,
-                        isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName
+                        isPermissionRequested = true,
+                        "updatePermissionFlagsForAllApps",
+                        packageName
                     )
                 }
             }
         }
     }
 
-    /**
-     * Update flags for a permission, without any validation on caller.
-     */
+    /** Update flags for a permission, without any validation on caller. */
     private fun MutateStateScope.updatePermissionFlags(
         appId: Int,
         userId: Int,
@@ -1311,20 +1436,19 @@
         methodName: String,
         packageName: String
     ) {
-        @Suppress("NAME_SHADOWING")
-        var flagMask = flagMask
-        @Suppress("NAME_SHADOWING")
-        var flagValues = flagValues
+        @Suppress("NAME_SHADOWING") var flagMask = flagMask
+        @Suppress("NAME_SHADOWING") var flagValues = flagValues
         // Only the system can change these flags and nothing else.
         if (!canUpdateSystemFlags) {
             // Different from the old implementation, which allowed non-system UIDs to remove (but
             // not add) permission restriction flags, we now consistently ignore them altogether.
-            val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
-                PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
-                PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
-                PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
-                PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
-                PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+            val ignoredMask =
+                PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
+                    PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
+                    PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+                    PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
+                    PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
+                    PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
             flagMask = flagMask andInv ignoredMask
             flagValues = flagValues andInv ignoredMask
         }
@@ -1340,7 +1464,8 @@
         val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
         if (!isPermissionRequested && oldFlags == 0) {
             Slog.w(
-                LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
+                LOG_TAG,
+                "$methodName: Permission $permissionName isn't requested by package" +
                     " $packageName"
             )
             return
@@ -1365,21 +1490,29 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = false, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = false,
+            enforceShellRestriction = false,
             "getAllowlistedRestrictedPermissions"
         )
 
         val callingUid = Binder.getCallingUid()
-        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
-            .use { it.getPackageState(packageName) } ?: return null
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(callingUid, userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return null
         val androidPackage = packageState.androidPackage ?: return null
 
-        val isCallerPrivileged = context.checkCallingOrSelfPermission(
-            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
-        ) == PackageManager.PERMISSION_GRANTED
+        val isCallerPrivileged =
+            context.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            ) == PackageManager.PERMISSION_GRANTED
 
-        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) &&
-            !isCallerPrivileged) {
+        if (
+            allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) &&
+                !isCallerPrivileged
+        ) {
             throw SecurityException(
                 "Querying system allowlist requires " +
                     Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
@@ -1389,8 +1522,12 @@
         val isCallerInstallerOnRecord =
             packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid)
 
-        if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
-                PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+        if (
+            allowlistedFlags.hasAnyBit(
+                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
+                    PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+            )
+        ) {
             if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
                 throw SecurityException(
                     "Querying upgrade or installer allowlist requires being installer on record" +
@@ -1400,7 +1537,9 @@
         }
 
         return getAllowlistedRestrictedPermissionsUnchecked(
-            packageState.appId, allowlistedFlags, userId
+            packageState.appId,
+            allowlistedFlags,
+            userId
         )
     }
 
@@ -1414,8 +1553,11 @@
             with(policy) { getPermissionFlags(appId, userId, permissionName) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
-                Slog.i(LOG_TAG, "$permissionName is not device aware permission, " +
-                        " get the flags for default device.")
+                Slog.i(
+                    LOG_TAG,
+                    "$permissionName is not device aware permission, " +
+                        " get the flags for default device."
+                )
                 return with(policy) { getPermissionFlags(appId, userId, permissionName) }
             }
             val virtualDeviceManagerInternal = virtualDeviceManagerInternal
@@ -1423,8 +1565,7 @@
                 Slog.e(LOG_TAG, "Virtual device manager service is not available.")
                 return 0
             }
-            val persistentDeviceId =
-                    virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
             if (persistentDeviceId != null) {
                 with(devicePolicy) {
                     getPermissionFlags(appId, persistentDeviceId, userId, permissionName)
@@ -1444,13 +1585,14 @@
         flags: Int
     ): Boolean {
         return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
-            with(policy) {
-               setPermissionFlags(appId, userId, permissionName, flags)
-            }
+            with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
-                Slog.i(LOG_TAG, "$permissionName is not device aware permission, " +
-                        " set the flags for default device.")
+                Slog.i(
+                    LOG_TAG,
+                    "$permissionName is not device aware permission, " +
+                        " set the flags for default device."
+                )
                 return with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
             }
 
@@ -1459,8 +1601,7 @@
                 Slog.e(LOG_TAG, "Virtual device manager service is not available.")
                 return false
             }
-            val persistentDeviceId =
-                    virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
             if (persistentDeviceId != null) {
                 with(devicePolicy) {
                     setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags)
@@ -1473,17 +1614,17 @@
     }
 
     /**
-     * This method does not enforce checks on the caller, should only be called after
-     * required checks.
+     * This method does not enforce checks on the caller, should only be called after required
+     * checks.
      */
     private fun getAllowlistedRestrictedPermissionsUnchecked(
         appId: Int,
         allowlistedFlags: Int,
         userId: Int
     ): ArrayList<String>? {
-        val permissionFlags = service.getState {
-            with(policy) { getUidPermissionFlags(appId, userId) }
-        } ?: return null
+        val permissionFlags =
+            service.getState { with(policy) { getUidPermissionFlags(appId, userId) } }
+                ?: return null
 
         var queryFlags = 0
         if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
@@ -1512,14 +1653,18 @@
             return false
         }
 
-        val permissionNames = getAllowlistedRestrictedPermissions(
-            packageName, allowlistedFlags, userId
-        ) ?: ArrayList(1)
+        val permissionNames =
+            getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId)
+                ?: ArrayList(1)
 
         if (permissionName !in permissionNames) {
             permissionNames += permissionName
             return setAllowlistedRestrictedPermissions(
-                packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true
+                packageName,
+                permissionNames,
+                allowlistedFlags,
+                userId,
+                isAddingPermission = true
             )
         }
         return false
@@ -1531,14 +1676,22 @@
         permissionNames: List<String>,
         userId: Int
     ) {
-        val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId,
-            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId
-        )?.let {
-            ArraySet(permissionNames).apply { this += it }.toList()
-        } ?: permissionNames
+        val newPermissionNames =
+            getAllowlistedRestrictedPermissionsUnchecked(
+                    appId,
+                    PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER,
+                    userId
+                )
+                ?.let { ArraySet(permissionNames).apply { this += it }.toList() }
+                ?: permissionNames
 
-        setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames,
-            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId)
+        setAllowlistedRestrictedPermissionsUnchecked(
+            androidPackage,
+            appId,
+            newPermissionNames,
+            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER,
+            userId
+        )
     }
 
     override fun removeAllowlistedRestrictedPermission(
@@ -1552,13 +1705,17 @@
             return false
         }
 
-        val permissions = getAllowlistedRestrictedPermissions(
-            packageName, allowlistedFlags, userId
-        ) ?: return false
+        val permissions =
+            getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId)
+                ?: return false
 
         if (permissions.remove(permissionName)) {
             return setAllowlistedRestrictedPermissions(
-                packageName, permissions, allowlistedFlags, userId, isAddingPermission = false
+                packageName,
+                permissions,
+                allowlistedFlags,
+                userId,
+                isAddingPermission = false
             )
         }
 
@@ -1572,16 +1729,22 @@
             return false
         }
 
-        if (packageManagerLocal.withFilteredSnapshot()
-                .use { it.getPackageState(permission.packageName) } == null) {
+        if (
+            packageManagerLocal.withFilteredSnapshot().use {
+                it.getPackageState(permission.packageName)
+            } == null
+        ) {
             return false
         }
 
         val isImmutablyRestrictedPermission =
             permission.isHardOrSoftRestricted && permission.isImmutablyRestricted
-        if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission(
-                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
-            ) != PackageManager.PERMISSION_GRANTED) {
+        if (
+            isImmutablyRestrictedPermission &&
+                context.checkCallingOrSelfPermission(
+                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+                ) != PackageManager.PERMISSION_GRANTED
+        ) {
             throw SecurityException(
                 "Cannot modify allowlist of an immutably restricted permission: ${permission.name}"
             )
@@ -1599,13 +1762,16 @@
     ): Boolean {
         Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1)
 
-        val isCallerPrivileged = context.checkCallingOrSelfPermission(
-            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
-        ) == PackageManager.PERMISSION_GRANTED
+        val isCallerPrivileged =
+            context.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            ) == PackageManager.PERMISSION_GRANTED
 
         val callingUid = Binder.getCallingUid()
-        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
-            .use { snapshot -> snapshot.packageStates[packageName] ?: return false }
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { snapshot ->
+                snapshot.packageStates[packageName] ?: return false
+            }
         val androidPackage = packageState.androidPackage ?: return false
 
         val isCallerInstallerOnRecord =
@@ -1627,15 +1793,19 @@
         }
 
         setAllowlistedRestrictedPermissionsUnchecked(
-            androidPackage, packageState.appId, permissionNames, allowlistedFlags, userId
+            androidPackage,
+            packageState.appId,
+            permissionNames,
+            allowlistedFlags,
+            userId
         )
 
         return true
     }
 
     /**
-     * This method does not enforce checks on the caller, should only be called after
-     * required checks.
+     * This method does not enforce checks on the caller, should only be called after required
+     * checks.
      */
     private fun setAllowlistedRestrictedPermissionsUnchecked(
         androidPackage: AndroidPackage,
@@ -1712,22 +1882,24 @@
                         }
                     }
 
-                    newFlags = if (permission.isHardRestricted && !isExempt) {
-                        newFlags or PermissionFlags.RESTRICTION_REVOKED
-                    } else {
-                        newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-                    }
-                    newFlags = if (permission.isSoftRestricted && !isExempt) {
-                        newFlags or PermissionFlags.SOFT_RESTRICTED
-                    } else {
-                        newFlags andInv PermissionFlags.SOFT_RESTRICTED
-                    }
-                    mask = mask or PermissionFlags.RESTRICTION_REVOKED or
-                        PermissionFlags.SOFT_RESTRICTED
+                    newFlags =
+                        if (permission.isHardRestricted && !isExempt) {
+                            newFlags or PermissionFlags.RESTRICTION_REVOKED
+                        } else {
+                            newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+                        }
+                    newFlags =
+                        if (permission.isSoftRestricted && !isExempt) {
+                            newFlags or PermissionFlags.SOFT_RESTRICTED
+                        } else {
+                            newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                        }
+                    mask =
+                        mask or
+                            PermissionFlags.RESTRICTION_REVOKED or
+                            PermissionFlags.SOFT_RESTRICTED
 
-                    updatePermissionFlags(
-                        appId, userId, requestedPermission, mask, newFlags
-                    )
+                    updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags)
                 }
             }
         }
@@ -1735,9 +1907,8 @@
 
     override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
         service.mutateState {
-            with(policy) {
-                resetRuntimePermissions(androidPackage.packageName, userId)
-            }
+            with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) }
+            with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) }
         }
     }
 
@@ -1745,9 +1916,8 @@
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
             service.mutateState {
                 snapshot.packageStates.forEach { (_, packageState) ->
-                    with(policy) {
-                        resetRuntimePermissions(packageState.packageName, userId)
-                    }
+                    with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
+                    with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) }
                 }
             }
         }
@@ -1755,7 +1925,8 @@
 
     override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
         context.enforceCallingOrSelfPermission(
-            Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, "addOnPermissionsChangeListener"
+            Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
+            "addOnPermissionsChangeListener"
         )
 
         onPermissionsChangeListeners.addListener(listener)
@@ -1780,9 +1951,7 @@
         requireNotNull(permissionName) { "permissionName cannot be null" }
         val packageNames = ArraySet<String>()
 
-        val permission = service.getState {
-            with(policy) { getPermissions()[permissionName] }
-        }
+        val permission = service.getState { with(policy) { getPermissions()[permissionName] } }
         if (permission == null || !permission.isAppOp) {
             packageNames.toTypedArray()
         }
@@ -1808,8 +1977,8 @@
                 androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName ->
                     val permission = permissions[permissionName] ?: return@requestedPermissions
                     if (permission.isAppOp) {
-                        val packageNames = appOpPermissionPackageNames
-                            .getOrPut(permissionName) { ArraySet() }
+                        val packageNames =
+                            appOpPermissionPackageNames.getOrPut(permissionName) { ArraySet() }
                         packageNames += androidPackage.packageName
                     }
                 }
@@ -1822,14 +1991,18 @@
         Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
         val backup = CompletableFuture<ByteArray>()
         permissionControllerManager.getRuntimePermissionBackup(
-            UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete
+            UserHandle.of(userId),
+            PermissionThread.getExecutor(),
+            backup::complete
         )
 
         return try {
             backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
         } catch (e: Exception) {
             when (e) {
-                is TimeoutException, is InterruptedException, is ExecutionException -> {
+                is TimeoutException,
+                is InterruptedException,
+                is ExecutionException -> {
                     Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
                     null
                 }
@@ -1846,7 +2019,8 @@
             isDelayedPermissionBackupFinished -= userId
         }
         permissionControllerManager.stageAndApplyRuntimePermissionsBackup(
-            backup, UserHandle.of(userId)
+            backup,
+            UserHandle.of(userId)
         )
     }
 
@@ -1860,7 +2034,9 @@
             }
         }
         permissionControllerManager.applyStagedRuntimePermissionBackup(
-            packageName, UserHandle.of(userId), PermissionThread.getExecutor()
+            packageName,
+            UserHandle.of(userId),
+            PermissionThread.getExecutor()
         ) { hasMoreBackup ->
             if (hasMoreBackup) {
                 return@applyStagedRuntimePermissionBackup
@@ -1907,21 +2083,15 @@
     ): IndexedMap<Int, MutableIndexedSet<String>> {
         val appIds = MutableIndexedSet<Int>()
 
-        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use {
-            it.packageStates
-        }
+        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
         state.userStates.forEachIndexed { _, _, userState ->
-            userState.appIdPermissionFlags.forEachIndexed { _, appId, _ ->
-                appIds.add(appId)
-            }
-            userState.appIdAppOpModes.forEachIndexed { _, appId, _ ->
-                appIds.add(appId)
-            }
-            userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ ->
+            userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> appIds.add(appId) }
+            userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> appIds.add(appId) }
+            userState.packageVersions.forEachIndexed packageVersions@{ _, packageName, _ ->
                 val appId = packageStates[packageName]?.appId ?: return@packageVersions
                 appIds.add(appId)
             }
-            userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ ->
+            userState.packageAppOpModes.forEachIndexed packageAppOpModes@{ _, packageName, _ ->
                 val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes
                 appIds.add(appId)
             }
@@ -1929,7 +2099,8 @@
 
         val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
         packageStates.forEach { (_, packageState) ->
-            appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() }
+            appIdPackageNames
+                .getOrPut(packageState.appId) { MutableIndexedSet() }
                 .add(packageState.packageName)
         }
         // add non-package app IDs which might not be reported by package manager.
@@ -1960,10 +2131,7 @@
         println("Permission groups:")
         withIndent {
             state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup ->
-                println(
-                    "${permissionGroup.name}: " +
-                        "packageName=${permissionGroup.packageName}"
-                )
+                println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}")
             }
         }
 
@@ -1992,7 +2160,9 @@
                     println("Permissions:")
                     withIndent {
                         userState.appIdPermissionFlags[appId]?.forEachIndexed {
-                            _, permissionName, flags ->
+                            _,
+                            permissionName,
+                            flags ->
                             val isGranted = PermissionFlags.isPermissionGranted(flags)
                             println(
                                 "$permissionName: granted=$isGranted, flags=" +
@@ -2002,7 +2172,9 @@
                     }
 
                     userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
-                            _, deviceId, devicePermissionFlags ->
+                        _,
+                        deviceId,
+                        devicePermissionFlags ->
                         println("Permissions (Device $deviceId):")
                         withIndent {
                             devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
@@ -2017,7 +2189,8 @@
 
                     println("App ops:")
                     withIndent {
-                        userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode ->
+                        userState.appIdAppOpModes[appId]?.forEachIndexed { _, appOpName, appOpMode
+                            ->
                             println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}")
                         }
                     }
@@ -2029,7 +2202,9 @@
                             println("App ops:")
                             withIndent {
                                 userState.packageAppOpModes[packageName]?.forEachIndexed {
-                                        _, appOpName, appOpMode ->
+                                    _,
+                                    appOpName,
+                                    appOpMode ->
                                     val modeName = AppOpsManager.modeToName(appOpMode)
                                     println("$appOpName: mode=$modeName")
                                 }
@@ -2048,24 +2223,30 @@
     }
 
     override fun getPermissionTEMP(permissionName: String): LegacyPermission2? {
-        val permission = service.getState {
-            with(policy) { getPermissions()[permissionName] }
-        } ?: return null
+        val permission =
+            service.getState { with(policy) { getPermissions()[permissionName] } } ?: return null
 
         return LegacyPermission2(
-            permission.permissionInfo, permission.type, permission.isReconciled, permission.appId,
-            permission.gids, permission.areGidsPerUser
+            permission.permissionInfo,
+            permission.type,
+            permission.isReconciled,
+            permission.appId,
+            permission.gids,
+            permission.areGidsPerUser
         )
     }
 
     override fun getLegacyPermissions(): List<LegacyPermission> =
-        service.getState {
-            with(policy) { getPermissions() }
-        }.mapIndexedTo(ArrayList()) { _, _, permission ->
-            LegacyPermission(
-                permission.permissionInfo, permission.type, permission.appId, permission.gids
-            )
-        }
+        service
+            .getState { with(policy) { getPermissions() } }
+            .mapIndexedTo(ArrayList()) { _, _, permission ->
+                LegacyPermission(
+                    permission.permissionInfo,
+                    permission.type,
+                    permission.appId,
+                    permission.gids
+                )
+            }
 
     override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
         // Package settings has been read when this method is called.
@@ -2086,9 +2267,7 @@
     ): List<LegacyPermission> =
         permissions.mapIndexedTo(ArrayList()) { _, _, permission ->
             // We don't need to provide UID and GIDs, which are only retrieved when dumping.
-            LegacyPermission(
-                permission.permissionInfo, permission.type, 0, EmptyArray.INT
-            )
+            LegacyPermission(permission.permissionInfo, permission.type, 0, EmptyArray.INT)
         }
 
     override fun getLegacyPermissionState(appId: Int): LegacyPermissionState {
@@ -2097,17 +2276,18 @@
         service.getState {
             val permissions = with(policy) { getPermissions() }
             userIds.forEachIndexed { _, userId ->
-                val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) }
-                    ?: return@forEachIndexed
+                val permissionFlags =
+                    with(policy) { getUidPermissionFlags(appId, userId) } ?: return@forEachIndexed
 
                 permissionFlags.forEachIndexed permissionFlags@{ _, permissionName, flags ->
                     val permission = permissions[permissionName] ?: return@permissionFlags
-                    val legacyPermissionState = LegacyPermissionState.PermissionState(
-                        permissionName,
-                        permission.isRuntime,
-                        PermissionFlags.isPermissionGranted(flags),
-                        PermissionFlags.toApiFlags(flags)
-                    )
+                    val legacyPermissionState =
+                        LegacyPermissionState.PermissionState(
+                            permissionName,
+                            permission.isRuntime,
+                            PermissionFlags.isPermissionGranted(flags),
+                            PermissionFlags.toApiFlags(flags)
+                        )
                     legacyState.putPermissionState(legacyPermissionState, userId)
                 }
             }
@@ -2131,16 +2311,13 @@
     override fun onSystemReady() {
         service.onSystemReady()
         virtualDeviceManagerInternal =
-                LocalServices.getService(VirtualDeviceManagerInternal::class.java)
-        permissionControllerManager = PermissionControllerManager(
-            context, PermissionThread.getHandler()
-        )
+            LocalServices.getService(VirtualDeviceManagerInternal::class.java)
+        permissionControllerManager =
+            PermissionControllerManager(context, PermissionThread.getHandler())
     }
 
     override fun onUserCreated(userId: Int) {
-        withCorkedPackageInfoCache {
-            service.onUserAdded(userId)
-        }
+        withCorkedPackageInfoCache { service.onUserAdded(userId) }
     }
 
     override fun onUserRemoved(userId: Int) {
@@ -2170,9 +2347,8 @@
             // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the
             // packages to be iterated in onStorageVolumeAdded() in the same order so that the
             // ownership of permissions is consistent.
-            storageVolumePackageNames.getOrPut(packageState.volumeUuid) {
-                mutableListOf()
-            } += packageState.packageName
+            storageVolumePackageNames.getOrPut(packageState.volumeUuid) { mutableListOf() } +=
+                packageState.packageName
             if (packageState.volumeUuid !in mountedStorageVolumes) {
                 // Wait for the storage volume to be mounted and batch the state mutation there.
                 return
@@ -2212,23 +2388,26 @@
                 return
             }
         }
-        val userIds = if (userId == UserHandle.USER_ALL) {
-            userManagerService.userIdsIncludingPreCreated
-        } else {
-            intArrayOf(userId)
-        }
+        val userIds =
+            if (userId == UserHandle.USER_ALL) {
+                userManagerService.userIdsIncludingPreCreated
+            } else {
+                intArrayOf(userId)
+            }
         @Suppress("NAME_SHADOWING")
-        userIds.forEach { userId ->
-            service.onPackageInstalled(androidPackage.packageName, userId)
-        }
+        userIds.forEach { userId -> service.onPackageInstalled(androidPackage.packageName, userId) }
 
         @Suppress("NAME_SHADOWING")
         userIds.forEach { userId ->
             // TODO: Remove when this callback receives packageState directly.
             val packageState =
                 packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!!
-            addAllowlistedRestrictedPermissionsUnchecked(androidPackage, packageState.appId,
-                params.allowlistedRestrictedPermissions, userId)
+            addAllowlistedRestrictedPermissionsUnchecked(
+                androidPackage,
+                packageState.appId,
+                params.allowlistedRestrictedPermissions,
+                userId
+            )
             setRequestedPermissionStates(packageState, userId, params.permissionStates)
         }
     }
@@ -2241,11 +2420,12 @@
         sharedUserPkgs: List<AndroidPackage>,
         userId: Int
     ) {
-        val userIds = if (userId == UserHandle.USER_ALL) {
-            userManagerService.userIdsIncludingPreCreated
-        } else {
-            intArrayOf(userId)
-        }
+        val userIds =
+            if (userId == UserHandle.USER_ALL) {
+                userManagerService.userIdsIncludingPreCreated
+            } else {
+                intArrayOf(userId)
+            }
         userIds.forEach { service.onPackageUninstalled(packageName, appId, it) }
         val packageState = packageManagerInternal.packageStates[packageName]
         if (packageState == null) {
@@ -2262,31 +2442,26 @@
         }
     }
 
-    /**
-     * Check whether a UID is root or system UID.
-     */
+    /** Check whether a UID is root or system UID. */
     private fun isRootOrSystemUid(uid: Int) =
         when (UserHandle.getAppId(uid)) {
-            Process.ROOT_UID, Process.SYSTEM_UID -> true
+            Process.ROOT_UID,
+            Process.SYSTEM_UID -> true
             else -> false
         }
 
-    /**
-     * Check whether a UID is shell UID.
-     */
+    /** Check whether a UID is shell UID. */
     private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID
 
-    /**
-     * Check whether a UID is root, system or shell UID.
-     */
+    /** Check whether a UID is root, system or shell UID. */
     private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid)
 
     /**
      * This method should typically only be used when granting or revoking permissions, since the
      * app may immediately restart after this call.
      *
-     * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against
-     * the app being relaunched.
+     * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against the
+     * app being relaunched.
      */
     private fun killUid(uid: Int, reason: String) {
         val activityManager = ActivityManager.getService()
@@ -2303,9 +2478,7 @@
         }
     }
 
-    /**
-     * @see PackageManagerLocal.withFilteredSnapshot
-     */
+    /** @see PackageManagerLocal.withFilteredSnapshot */
     private fun PackageManagerLocal.withFilteredSnapshot(
         callingUid: Int,
         userId: Int
@@ -2323,35 +2496,27 @@
         packageName: String
     ): PackageState? = packageStates[packageName]
 
-    /**
-     * Check whether a UID belongs to an instant app.
-     */
+    /** Check whether a UID belongs to an instant app. */
     private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean =
         // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the
         // API for getting the SharedUserApi object for an app ID yet, so for now we just keep
         // calling the old API.
         packageManagerInternal.getInstantAppPackageName(uid) != null
 
-    /**
-     * Check whether a package is visible to a UID within the same user as the UID.
-     */
+    /** Check whether a package is visible to a UID within the same user as the UID. */
     private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
         packageName: String,
         uid: Int
     ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid)
 
-    /**
-     * Check whether a package in a particular user is visible to a UID.
-     */
+    /** Check whether a package in a particular user is visible to a UID. */
     private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
         packageName: String,
         userId: Int,
         uid: Int
     ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null }
 
-    /**
-     * @see PackageManagerLocal.UnfilteredSnapshot.filtered
-     */
+    /** @see PackageManagerLocal.UnfilteredSnapshot.filtered */
     private fun PackageManagerLocal.UnfilteredSnapshot.filtered(
         callingUid: Int,
         userId: Int
@@ -2374,13 +2539,16 @@
         val callingUid = Binder.getCallingUid()
         val callingUserId = UserHandle.getUserId(callingUid)
         if (userId != callingUserId) {
-            val permissionName = if (enforceFullPermission) {
-                Manifest.permission.INTERACT_ACROSS_USERS_FULL
-            } else {
-                Manifest.permission.INTERACT_ACROSS_USERS
-            }
-            if (context.checkCallingOrSelfPermission(permissionName) !=
-                PackageManager.PERMISSION_GRANTED) {
+            val permissionName =
+                if (enforceFullPermission) {
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL
+                } else {
+                    Manifest.permission.INTERACT_ACROSS_USERS
+                }
+            if (
+                context.checkCallingOrSelfPermission(permissionName) !=
+                    PackageManager.PERMISSION_GRANTED
+            ) {
                 val exceptionMessage = buildString {
                     if (message != null) {
                         append(message)
@@ -2397,9 +2565,11 @@
             }
         }
         if (enforceShellRestriction && isShellUid(callingUid)) {
-            val isShellRestricted = userManagerInternal.hasUserRestriction(
-                UserManager.DISALLOW_DEBUGGING_FEATURES, userId
-            )
+            val isShellRestricted =
+                userManagerInternal.hasUserRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES,
+                    userId
+                )
             if (isShellRestricted) {
                 val exceptionMessage = buildString {
                     if (message != null) {
@@ -2424,10 +2594,11 @@
         message: String?,
         vararg permissionNames: String
     ) {
-        val hasAnyPermission = permissionNames.any { permissionName ->
-            context.checkCallingOrSelfPermission(permissionName) ==
-                PackageManager.PERMISSION_GRANTED
-        }
+        val hasAnyPermission =
+            permissionNames.any { permissionName ->
+                context.checkCallingOrSelfPermission(permissionName) ==
+                    PackageManager.PERMISSION_GRANTED
+            }
         if (!hasAnyPermission) {
             val exceptionMessage = buildString {
                 if (message != null) {
@@ -2443,9 +2614,7 @@
         }
     }
 
-    /**
-     * Callback invoked when interesting actions have been taken on a permission.
-     */
+    /** Callback invoked when interesting actions have been taken on a permission. */
     private inner class OnPermissionFlagsChangedListener :
         AppIdPermissionPolicy.OnPermissionFlagsChangedListener() {
         private var isPermissionFlagsChanged = false
@@ -2476,9 +2645,8 @@
             isPermissionFlagsChanged = true
 
             val uid = UserHandle.getUid(userId, appId)
-            val permission = service.getState {
-                with(policy) { getPermissions()[permissionName] }
-            } ?: return
+            val permission =
+                service.getState { with(policy) { getPermissions()[permissionName] } } ?: return
             val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags)
             val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags)
 
@@ -2512,16 +2680,20 @@
             runtimePermissionChangedUids.clear()
 
             if (!isKillRuntimePermissionRevokedUidsSkipped) {
-                val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) {
-                    killRuntimePermissionRevokedUidsReasons.joinToString(", ")
-                } else {
-                    PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
-                }
+                val reason =
+                    if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) {
+                        killRuntimePermissionRevokedUidsReasons.joinToString(", ")
+                    } else {
+                        PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
+                    }
                 runtimePermissionRevokedUids.forEachIndexed {
-                    _, uid, areOnlyNotificationsPermissionsRevoked ->
+                    _,
+                    uid,
+                    areOnlyNotificationsPermissionsRevoked ->
                     handler.post {
-                        if (areOnlyNotificationsPermissionsRevoked &&
-                            isAppBackupAndRestoreRunning(uid)
+                        if (
+                            areOnlyNotificationsPermissionsRevoked &&
+                                isAppBackupAndRestoreRunning(uid)
                         ) {
                             return@post
                         }
@@ -2541,19 +2713,27 @@
         }
 
         private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
-            if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
-                PackageManager.PERMISSION_GRANTED) {
+            if (
+                checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
+                    PackageManager.PERMISSION_GRANTED
+            ) {
                 return false
             }
             return try {
                 val contentResolver = context.contentResolver
                 val userId = UserHandle.getUserId(uid)
-                val isInSetup = Settings.Secure.getIntForUser(
-                    contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId
-                ) == 0
-                val isInDeferredSetup = Settings.Secure.getIntForUser(
-                    contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId
-                ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
+                val isInSetup =
+                    Settings.Secure.getIntForUser(
+                        contentResolver,
+                        Settings.Secure.USER_SETUP_COMPLETE,
+                        userId
+                    ) == 0
+                val isInDeferredSetup =
+                    Settings.Secure.getIntForUser(
+                        contentResolver,
+                        Settings.Secure.USER_SETUP_PERSONALIZATION_STATE,
+                        userId
+                    ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
                 isInSetup || isInDeferredSetup
             } catch (e: Settings.SettingNotFoundException) {
                 Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e")
@@ -2614,31 +2794,34 @@
         @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
         private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
 
-        private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply {
-            this[Manifest.permission.ACCESS_COARSE_LOCATION] =
-                Manifest.permission.ACCESS_FINE_LOCATION
-            this[Manifest.permission.INTERACT_ACROSS_USERS] =
-                Manifest.permission.INTERACT_ACROSS_USERS_FULL
-        }
+        private val FULLER_PERMISSIONS =
+            ArrayMap<String, String>().apply {
+                this[Manifest.permission.ACCESS_COARSE_LOCATION] =
+                    Manifest.permission.ACCESS_FINE_LOCATION
+                this[Manifest.permission.INTERACT_ACROSS_USERS] =
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL
+            }
 
-        private val NOTIFICATIONS_PERMISSIONS = arraySetOf(
-            Manifest.permission.POST_NOTIFICATIONS
-        )
+        private val NOTIFICATIONS_PERMISSIONS = arraySetOf(Manifest.permission.POST_NOTIFICATIONS)
 
-        private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or
-            PermissionFlags.IMPLICIT
-        private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or
-            PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or
-            PermissionFlags.USER_FIXED
+        private const val REVIEW_REQUIRED_FLAGS =
+            PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
+        private const val UNREQUESTABLE_MASK =
+            PermissionFlags.RESTRICTION_REVOKED or
+                PermissionFlags.SYSTEM_FIXED or
+                PermissionFlags.POLICY_FIXED or
+                PermissionFlags.USER_FIXED
 
         private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
 
-        /** Cap the size of permission trees that 3rd party apps can define; in characters of text  */
+        /**
+         * Cap the size of permission trees that 3rd party apps can define; in characters of text
+         */
         private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768
 
         private const val PERMISSION_ALLOWLIST_MASK =
             PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
-            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
-            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+                PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
+                PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index bd82935..6b20ef1 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -25,9 +25,7 @@
 import java.io.FileOutputStream
 import java.io.IOException
 
-/**
- * Read from an [AtomicFile], fallback to reserve file to read the data.
- */
+/** Read from an [AtomicFile], fallback to reserve file to read the data. */
 @Throws(Exception::class)
 inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) {
     try {
@@ -46,9 +44,7 @@
     }
 }
 
-/**
- * Write to actual file and reserve file.
- */
+/** Write to actual file and reserve file. */
 @Throws(IOException::class)
 inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
     val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
@@ -66,9 +62,7 @@
     }
 }
 
-/**
- * Write to an [AtomicFile] and close everything safely when done.
- */
+/** Write to an [AtomicFile] and close everything safely when done. */
 @Throws(IOException::class)
 // Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline.
 inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) {
diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
index 1d27aef..6ab73c7 100644
--- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
@@ -22,9 +22,7 @@
 import org.xmlpull.v1.XmlPullParser
 import org.xmlpull.v1.XmlPullParserException
 
-/**
- * Parse content from [InputStream] with [BinaryXmlPullParser].
- */
+/** Parse content from [InputStream] with [BinaryXmlPullParser]. */
 @Throws(IOException::class, XmlPullParserException::class)
 inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) {
     BinaryXmlPullParser().apply {
@@ -35,6 +33,7 @@
 
 /**
  * Iterate through child tags of the current tag.
+ *
  * <p>
  * Attributes for the current tag needs to be accessed before this method is called because this
  * method will advance the parser past the start tag of the current tag. The code inspecting each
@@ -50,7 +49,8 @@
 inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) {
     when (val eventType = eventType) {
         // Document start or start tag of the parent tag.
-        XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd()
+        XmlPullParser.START_DOCUMENT,
+        XmlPullParser.START_TAG -> nextTagOrEnd()
         else -> throw XmlPullParserException("Unexpected event type $eventType")
     }
     while (true) {
@@ -90,7 +90,8 @@
                 nextTagOrEnd()
             }
             // End tag of the parent tag, or document end.
-            XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break
+            XmlPullParser.END_TAG,
+            XmlPullParser.END_DOCUMENT -> break
             else -> throw XmlPullParserException("Unexpected event type $eventType")
         }
     }
@@ -107,193 +108,146 @@
 inline fun BinaryXmlPullParser.nextTagOrEnd(): Int {
     while (true) {
         when (val eventType = next()) {
-            XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT ->
-                return eventType
+            XmlPullParser.START_TAG,
+            XmlPullParser.END_TAG,
+            XmlPullParser.END_DOCUMENT -> return eventType
             else -> continue
         }
     }
 }
 
-/**
- * @see BinaryXmlPullParser.getName
- */
+/** @see BinaryXmlPullParser.getName */
 inline val BinaryXmlPullParser.tagName: String
     get() = name
 
-/**
- * Check whether an attribute exists for the current tag.
- */
+/** Check whether an attribute exists for the current tag. */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1
 
-/**
- * @see BinaryXmlPullParser.getAttributeIndex
- */
+/** @see BinaryXmlPullParser.getAttributeIndex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeIndexOrThrow
- */
+/** @see BinaryXmlPullParser.getAttributeIndexOrThrow */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int =
     getAttributeIndexOrThrow(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeValue
- */
+/** @see BinaryXmlPullParser.getAttributeValue */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? =
     getAttributeValue(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeValue
- */
+/** @see BinaryXmlPullParser.getAttributeValue */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String =
     getAttributeValue(getAttributeIndexOrThrow(name))
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesHex
- */
+/** @see BinaryXmlPullParser.getAttributeBytesHex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? =
     getAttributeBytesHex(null, name, null)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesHex
- */
+/** @see BinaryXmlPullParser.getAttributeBytesHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray =
     getAttributeBytesHex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesBase64
- */
+/** @see BinaryXmlPullParser.getAttributeBytesBase64 */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? =
     getAttributeBytesBase64(null, name, null)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesBase64
- */
+/** @see BinaryXmlPullParser.getAttributeBytesBase64 */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray =
     getAttributeBytesBase64(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeInt
- */
+/** @see BinaryXmlPullParser.getAttributeInt */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int =
     getAttributeInt(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeInt
- */
+/** @see BinaryXmlPullParser.getAttributeInt */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int =
     getAttributeInt(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeIntHex
- */
+/** @see BinaryXmlPullParser.getAttributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int =
     getAttributeIntHex(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeIntHex
- */
+/** @see BinaryXmlPullParser.getAttributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int =
     getAttributeIntHex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLong
- */
+/** @see BinaryXmlPullParser.getAttributeLong */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long =
     getAttributeLong(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLong
- */
+/** @see BinaryXmlPullParser.getAttributeLong */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long =
     getAttributeLong(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLongHex
- */
+/** @see BinaryXmlPullParser.getAttributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault(
     name: String,
     defaultValue: Long
 ): Long = getAttributeLongHex(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLongHex
- */
+/** @see BinaryXmlPullParser.getAttributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long =
     getAttributeLongHex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeFloat
- */
+/** @see BinaryXmlPullParser.getAttributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeFloatOrDefault(
     name: String,
     defaultValue: Float
 ): Float = getAttributeFloat(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeFloat
- */
+/** @see BinaryXmlPullParser.getAttributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float =
     getAttributeFloat(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeDouble
- */
+/** @see BinaryXmlPullParser.getAttributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault(
     name: String,
     defaultValue: Double
 ): Double = getAttributeDouble(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeDouble
- */
+/** @see BinaryXmlPullParser.getAttributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double =
     getAttributeDouble(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBoolean
- */
+/** @see BinaryXmlPullParser.getAttributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault(
     name: String,
     defaultValue: Boolean
 ): Boolean = getAttributeBoolean(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBoolean
- */
+/** @see BinaryXmlPullParser.getAttributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean =
diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
index c8cd586..6500a7d 100644
--- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
@@ -20,9 +20,7 @@
 import java.io.IOException
 import java.io.OutputStream
 
-/**
- * Serialize content into [OutputStream] with [BinaryXmlSerializer].
- */
+/** Serialize content into [OutputStream] with [BinaryXmlSerializer]. */
 @Throws(IOException::class)
 inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) {
     BinaryXmlSerializer().apply {
@@ -57,54 +55,42 @@
     endTag(null, name)
 }
 
-/**
- * @see BinaryXmlSerializer.attribute
- */
+/** @see BinaryXmlSerializer.attribute */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attribute(name: String, value: String) {
     attribute(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeInterned
- */
+/** @see BinaryXmlSerializer.attributeInterned */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) {
     attributeInterned(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBytesHex
- */
+/** @see BinaryXmlSerializer.attributeBytesHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) {
     attributeBytesHex(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBytesBase64
- */
+/** @see BinaryXmlSerializer.attributeBytesBase64 */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) {
     attributeBytesBase64(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeInt
- */
+/** @see BinaryXmlSerializer.attributeInt */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) {
     attributeInt(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeInt
- */
+/** @see BinaryXmlSerializer.attributeInt */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeIntWithDefault(
@@ -117,18 +103,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeIntHex
- */
+/** @see BinaryXmlSerializer.attributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) {
     attributeIntHex(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeIntHex
- */
+/** @see BinaryXmlSerializer.attributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeIntHexWithDefault(
@@ -141,18 +123,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLong
- */
+/** @see BinaryXmlSerializer.attributeLong */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) {
     attributeLong(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLong
- */
+/** @see BinaryXmlSerializer.attributeLong */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLongWithDefault(
@@ -165,18 +143,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLongHex
- */
+/** @see BinaryXmlSerializer.attributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) {
     attributeLongHex(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLongHex
- */
+/** @see BinaryXmlSerializer.attributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLongHexWithDefault(
@@ -189,18 +163,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeFloat
- */
+/** @see BinaryXmlSerializer.attributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) {
     attributeFloat(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeFloat
- */
+/** @see BinaryXmlSerializer.attributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeFloatWithDefault(
@@ -213,18 +183,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeDouble
- */
+/** @see BinaryXmlSerializer.attributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) {
     attributeDouble(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeDouble
- */
+/** @see BinaryXmlSerializer.attributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeDoubleWithDefault(
@@ -237,18 +203,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBoolean
- */
+/** @see BinaryXmlSerializer.attributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) {
     attributeBoolean(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBoolean
- */
+/** @see BinaryXmlSerializer.attributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBooleanWithDefault(
diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
index a61489c..3ef284b 100644
--- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
@@ -22,13 +22,13 @@
 
 object PackageVersionMigration {
     /**
-     * Maps existing permission and app-op version to a unified version during OTA upgrade. The
-     * new unified version is used in determining the upgrade steps for a package (for both
-     * permission and app-ops).
+     * Maps existing permission and app-op version to a unified version during OTA upgrade. The new
+     * unified version is used in determining the upgrade steps for a package (for both permission
+     * and app-ops).
      *
      * @return unified permission/app-op version
      * @throws IllegalStateException if the method is called when there is nothing to migrate i.e.
-     * permission and app-op file does not exist.
+     *   permission and app-op file does not exist.
      */
     internal fun getVersion(userId: Int): Int {
         val permissionMigrationHelper =
diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
index e6b4e3e..3d835e8 100644
--- a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
+++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
@@ -23,15 +23,11 @@
 object PermissionApex {
     private const val MODULE_NAME = "com.android.permission"
 
-    /**
-     * @see ApexEnvironment.getDeviceProtectedDataDir
-     */
+    /** @see ApexEnvironment.getDeviceProtectedDataDir */
     val systemDataDirectory: File
         get() = apexEnvironment.deviceProtectedDataDir
 
-    /**
-     * @see ApexEnvironment.getDeviceProtectedDataDirForUser
-     */
+    /** @see ApexEnvironment.getDeviceProtectedDataDirForUser */
     fun getUserDataDirectory(userId: Int): File =
         apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId))
 
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
index ca57f51..8b5d7f0 100644
--- a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -31,6 +31,7 @@
 import android.app.smartspace.SmartspaceConfig;
 import android.app.smartspace.SmartspaceSessionId;
 import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.flags.Flags;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -165,7 +166,8 @@
             }
             Context ctx = getContext();
             if (!(ctx.checkCallingPermission(MANAGE_SMARTSPACE) == PERMISSION_GRANTED
-                    || ctx.checkCallingPermission(ACCESS_SMARTSPACE) == PERMISSION_GRANTED
+                    || (Flags.accessSmartspace()
+                    && ctx.checkCallingPermission(ACCESS_SMARTSPACE) == PERMISSION_GRANTED)
                     || mServiceNameResolver.isTemporary(userId)
                     || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 6ff7b26..b63a58a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -37,6 +37,7 @@
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.WindowManagerGlobal;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -598,6 +599,28 @@
                 false /* orientationPortrait */);
     }
 
+    @Test
+    public void switchesKeyboardLayout_withShortcut_onlyIfImeVisible() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+                new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+                        KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+                        KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isFalse();
+
+        verifyInputViewStatusOnMainSync(
+                () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+        assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+                new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+                        KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+                        KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isTrue();
+    }
+
     /**
      * This checks that when the system navigation bar is not created (e.g. emulator),
      * then the IME caption bar is also not created.
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index f3ac7d5..12cd0f6 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -143,8 +143,8 @@
             val result: Result,
             val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
         ) {
-            constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
-                    : this(pkgName, appType, Result.Exception(exception))
+            constructor(pkgName: String, appType: AppType, exception: Class<out Exception>) :
+                    this(pkgName, appType, Result.Exception(exception))
 
             val expectedLabel = when (result) {
                 Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
@@ -299,11 +299,9 @@
                     .hideAsFinal()
 
     private fun makePkgSetting(pkgName: String, pkg: AndroidPackageInternal) =
-        PackageSetting(
-            pkgName, null, File("/test"),
-            null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
-            UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
-        ).apply {
+        PackageSetting(pkgName, null, File("/test"), 0, 0,
+                UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c"))
+        .apply {
             if (params.isSystem) {
                 this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
             }
@@ -373,7 +371,7 @@
             whenever(this.isCallerRecents(anyInt())) { false }
         }
         val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
-            whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(), 
+            whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
                     any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
             whenever(this.snapshot()) { this@mockThrowOnUnmocked }
             whenever(registerObserver(any())).thenCallRealMethod()
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 6eacef7..c617ec4 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -58,6 +58,7 @@
         ":PackageManagerTestOverlayTarget",
         ":PackageManagerTestOverlayTargetNoOverlayable",
         ":PackageManagerTestAppDeclaresStaticLibrary",
+        ":PackageManagerTestAppDifferentPkgName",
         ":PackageManagerTestAppStub",
         ":PackageManagerTestAppUsesStaticLibrary",
         ":PackageManagerTestAppVersion1",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
index c490604..304f605 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
@@ -44,6 +44,10 @@
         private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk"
         private const val VERSION_TWO_ALT_KEY_IDSIG =
                 "PackageManagerTestAppVersion2AltKey.apk.idsig"
+
+        private const val ANOTHER_PKG_NAME = "com.android.server.pm.test.test_app2"
+        private const val ANOTHER_PKG = "PackageManagerTestAppDifferentPkgName.apk"
+
         private const val STRICT_SIGNATURE_CONFIG_PATH =
                 "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml"
         private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref"
@@ -74,6 +78,7 @@
     @After
     fun removeApk() {
         device.uninstallPackage(TEST_PKG_NAME)
+        device.uninstallPackage(ANOTHER_PKG_NAME)
     }
 
     @Before
@@ -90,7 +95,9 @@
                     .readText()
                     .replace(
                         "</config>",
-                            "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>"
+                            "<require-strict-signature package=\"${TEST_PKG_NAME}\"/>" +
+                            "<require-strict-signature package=\"${ANOTHER_PKG_NAME}\"/>" +
+                            "</config>"
                     )
             writeText(newConfigText)
         }
@@ -146,10 +153,7 @@
                 tempFolder.newFile()
         )
         assertThat(device.installPackage(versionTwoFile, true)).isNull()
-        val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}")
-                .lineSequence()
-                .first()
-                .replace("package:", "")
+        val baseApkPath = getBaseApkPath(TEST_PKG_NAME)
         assertThat(baseApkPath).doesNotContain(productPath.toString())
         preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig")
 
@@ -175,4 +179,23 @@
         assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
                 .contains(productPath.toString())
     }
+
+    @Test
+    fun allowlistedPackageIsNotASystemApp() {
+        // If an allowlisted package isn't a system app, make sure install and boot still works
+        // normally.
+        assertThat(device.installJavaResourceApk(tempFolder, ANOTHER_PKG, /* reinstall */ false))
+                .isNull()
+        assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+
+        preparer.reboot()
+        assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+    }
+
+    private fun getBaseApkPath(pkgName: String): String {
+        return device.executeShellCommand("pm path $pkgName")
+                .lineSequence()
+                .first()
+                .replace("package:", "")
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index bee7c40..b826590 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -76,3 +76,11 @@
     certificate: ":FrameworksServicesTests_keyset_A_cert",
     v4_signature: true,
 }
+
+android_test_helper_app {
+    name: "PackageManagerTestAppDifferentPkgName",
+    manifest: "AndroidManifestDifferentPkgName.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
new file mode 100644
index 0000000..0c5d36e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.test_app2"
+    android:versionCode="1"
+    >
+
+    <permission
+        android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
+        android:protectionLevel="normal"
+        />
+
+    <application>
+        <activity android:name="com.android.server.pm.test.test_app.TestActivity"
+            android:label="PackageManagerTestApp" />
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2889c74..2810145 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -24,7 +24,9 @@
 import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
 import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
 import static android.content.res.Resources.ID_NULL;
+
 import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
+
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -41,6 +43,7 @@
 
 import android.annotation.NonNull;
 import android.app.PropertyInvalidatedCache;
+import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
@@ -871,12 +874,20 @@
                 .setUid(packageSetting.getAppId())
                 .hideAsFinal());
 
-        ArchiveState archiveState = new ArchiveState(
-                List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
-                                Path.of("/monochromePath1")),
-                        new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
-                                Path.of("/monochromePath2"))),
-                "installerTitle");
+        ArchiveState archiveState =
+                new ArchiveState(
+                        List.of(
+                                new ArchiveState.ArchiveActivityInfo(
+                                        "title1",
+                                        new ComponentName("pkg1", "class1"),
+                                        Path.of("/path1"),
+                                        Path.of("/monochromePath1")),
+                                new ArchiveState.ArchiveActivityInfo(
+                                        "title2",
+                                        new ComponentName("pkg2", "class2"),
+                                        Path.of("/path2"),
+                                        Path.of("/monochromePath2"))),
+                        "installerTitle");
         packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
                 archiveState);
         settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
@@ -956,20 +967,12 @@
                 PACKAGE_NAME,
                 REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE);
         origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
         final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
         verifySettingCopy(origPkgSetting01, testPkgSetting01);
@@ -978,23 +981,15 @@
     @Test
     public void testPackageStateCopy02() {
         final PackageSetting origPkgSetting01 = new PackageSetting(
-                PACKAGE_NAME /*pkgName*/,
-                REAL_PACKAGE_NAME /*realPkgName*/,
+                PACKAGE_NAME,
+                REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE);
         origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, 0, null, false,
                 false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
                 new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
@@ -1017,20 +1012,10 @@
                 PACKAGE_NAME /*pkgName*/,
                 REAL_PACKAGE_NAME /*realPkgName*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                null /*primaryCpuAbiString*/,
-                null /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                UPDATED_VERSION_CODE,
                 0 /*pkgFlags*/,
                 0 /*pkgPrivateFlags*/,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setLongVersionCode(UPDATED_VERSION_CODE);
         testPkgSetting01.copyPackageSetting(origPkgSetting01, true);
         verifySettingCopy(origPkgSetting01, testPkgSetting01);
         verifyUserStatesCopy(origPkgSetting01.readUserState(0),
@@ -1065,7 +1050,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1103,7 +1090,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1143,7 +1132,9 @@
                     null /*usesStaticLibraries*/,
                     null /*usesStaticLibrariesVersions*/,
                     null /*mimeGroups*/,
-                    UUID.randomUUID());
+                    UUID.randomUUID(),
+                    34 /*targetSdkVersion*/,
+                    null /*restrictUpdateHash*/);
             fail("Expected a PackageManagerException");
         } catch (PackageManagerException expected) {
         }
@@ -1179,7 +1170,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
         assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
@@ -1224,7 +1217,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(0));
         assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1269,7 +1264,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(10064));
         assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1315,7 +1312,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(10064));
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1358,7 +1357,9 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(0));
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1682,20 +1683,13 @@
                 PACKAGE_NAME,
                 REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 pkgFlags,
                 0 /*privateFlags*/,
-                sharedUserId,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE)
+                .setSharedUserAppId(sharedUserId);
     }
 
     private PackageSetting createPackageSetting(String packageName) {
@@ -1703,20 +1697,12 @@
                 packageName,
                 packageName,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 0,
                 0 /*privateFlags*/,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE);
     }
 
     static @NonNull List<UserInfo> createFakeUsers() {
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 58ae740..87a297b 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
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.overlay.OverlayPaths;
@@ -192,8 +193,8 @@
         return new SuspendParams(dialogInfo, appExtras, launcherExtras);
     }
 
-    private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
-            String sValue, String dKey, double dValue) {
+    private static PersistableBundle createPersistableBundle(
+            String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) {
         final PersistableBundle result = new PersistableBundle(3);
         if (lKey != null) {
             result.putLong("com.unit_test." + lKey, lValue);
@@ -320,6 +321,7 @@
             assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]);
         }
     }
+
     private static void assertLastPackageUsageSet(
             PackageStateUnserialized state, int reason, long value) throws Exception {
         for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) {
@@ -330,6 +332,7 @@
             }
         }
     }
+
     @Test
     public void testPackageUseReasons() throws Exception {
         PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
@@ -377,6 +380,7 @@
         assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build()));
         assertFalse(testState.setOverlayPaths(null));
     }
+
     @Test
     public void testSharedLibOverlayPaths() {
         final PackageUserStateImpl testState = new PackageUserStateImpl();
@@ -401,8 +405,12 @@
     @Test
     public void archiveState() {
         PackageUserStateImpl packageUserState = new PackageUserStateImpl();
-        ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
-                "appTitle", Path.of("/path1"), Path.of("/path2"));
+        ArchiveState.ArchiveActivityInfo archiveActivityInfo =
+                new ArchiveState.ArchiveActivityInfo(
+                        "appTitle",
+                        new ComponentName("pkg", "class"),
+                        Path.of("/path1"),
+                        Path.of("/path2"));
         ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
                 "installerTitle");
         packageUserState.setArchiveState(archiveState);
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 0275c7d..6e4069f 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -40,6 +40,7 @@
         "services.core",
         "servicestests-utils",
         "testables",
+        "TestParameterInjector",
     ],
 
     defaults: [
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 55fde00..e71ea26 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.MANAGE_USB" />
 
     <!-- Permissions needed for DisplayTransformManagerTest -->
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 4b124ca..8cc3408 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -100,6 +100,14 @@
     }
 
     @Test
+    public void testPowerThrottlingMapId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(5);
+
+        assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId());
+        assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId());
+    }
+
+    @Test
     public void testRearDisplayLayout() {
         Layout configLayout = mDeviceStateToLayoutMap.get(2);
 
@@ -133,13 +141,15 @@
                 mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
                 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
                 /* refreshRateZoneId= */ "zone1",
-                /* refreshRateThermalThrottlingMapId= */ "rr1");
+                /* refreshRateThermalThrottlingMapId= */ "rr1",
+                /* powerThrottlingMapId= */ "power1");
         testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
                 /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
                 mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
                 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
                 /* refreshRateZoneId= */ "zone2",
-                /* refreshRateThermalThrottlingMapId= */ "rr2");
+                /* refreshRateThermalThrottlingMapId= */ "rr2",
+                /* powerThrottlingMapId= */ "power2");
         testLayout.postProcessLocked();
 
         assertEquals(testLayout, configLayout);
@@ -200,7 +210,8 @@
                     mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
                     DisplayAddress.fromPhysicalDisplayId(123L),
                     /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                    /* refreshRateThermalThrottlingMapId= */ null));
+                    /* refreshRateThermalThrottlingMapId= */ null,
+                    /* powerThrottlingMapId= */ null));
     }
 
     @Test
@@ -215,7 +226,8 @@
                     mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
                     DisplayAddress.fromPhysicalDisplayId(987L),
                     /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                    /* refreshRateThermalThrottlingMapId= */ null));
+                    /* refreshRateThermalThrottlingMapId= */ null,
+                    /* powerThrottlingMapId= */ null));
     }
 
     @Test
@@ -271,7 +283,8 @@
                 enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
                 leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
                 /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ null);
     }
 
     private void setupDeviceStateToLayoutMap() throws IOException {
@@ -327,7 +340,6 @@
                 +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
                 +      "</display>\n"
                 +    "</layout>\n"
-
                 +    "<layout>\n"
                 +      "<state>3</state> \n"
                 +      "<display enabled=\"true\" defaultDisplay=\"true\" "
@@ -338,7 +350,6 @@
                 +        "<address>678</address>\n"
                 +      "</display>\n"
                 +    "</layout>\n"
-
                 +    "<layout>\n"
                 +      "<state>4</state> \n"
                 +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
@@ -352,6 +363,20 @@
                 +      "</display>\n"
                 +    "</layout>\n"
                 +    "<layout>\n"
+                +      "<state>5</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>345</address>\n"
+                +        "<position>front</position>\n"
+                +        "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +        "<position>rear</position>\n"
+                +        "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
                 +      "<state>99</state> \n"
                 +      "<display enabled=\"true\" defaultDisplay=\"true\" "
                 +                                          "refreshRateZoneId=\"zone1\">\n"
@@ -361,6 +386,7 @@
                 +         "<refreshRateThermalThrottlingMapId>"
                 +           "rr1"
                 +         "</refreshRateThermalThrottlingMapId>"
+                +         "<powerThrottlingMapId>power1</powerThrottlingMapId>\n"
                 +       "</display>\n"
                 +       "<display enabled=\"false\" displayGroup=\"group1\" "
                 +                                           "refreshRateZoneId=\"zone2\">\n"
@@ -370,6 +396,7 @@
                 +         "<refreshRateThermalThrottlingMapId>"
                 +           "rr2"
                 +         "</refreshRateThermalThrottlingMapId>"
+                +         "<powerThrottlingMapId>power2</powerThrottlingMapId>\n"
                 +       "</display>\n"
                 +     "</layout>\n"
                 +   "</layouts>\n";
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index c12aedb..a400f12 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -90,7 +90,9 @@
                 .append("\n    isSlowChange:")
                 .append(displayBrightnessState.isSlowChange())
                 .append("\n    maxBrightness:")
-                .append(displayBrightnessState.getMaxBrightness());
+                .append(displayBrightnessState.getMaxBrightness())
+                .append("\n    customAnimationRate:")
+                .append(displayBrightnessState.getCustomAnimationRate());
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c37d21a..179a9d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -570,9 +570,9 @@
         assertNotNull(data);
         assertEquals(2, data.mMaxBrightnessLimits.size());
         assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
-        assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+        assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
         assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
-        assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+        assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
 
         assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
         assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
@@ -841,9 +841,9 @@
               + "        </point>\n"
               + "    </brightnessMap>\n"
               + "    <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
-              + "    <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+              + "    <screenBrightnessRampIncrease>0.11</screenBrightnessRampIncrease>\n"
               + "    <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
-              + "    <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+              + "    <screenBrightnessRampDecrease>0.1</screenBrightnessRampDecrease>\n"
               + "</hdrBrightnessConfig>";
     }
 
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 2396905..16d72e4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -114,17 +114,17 @@
 import com.android.server.display.DisplayManagerService.DeviceStateListener;
 import com.android.server.display.DisplayManagerService.SyncRoot;
 import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import com.google.common.truth.Expect;
+
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
-import com.google.common.truth.Expect;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -201,9 +201,12 @@
                 @Override
                 LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                         Handler handler, DisplayAdapter.Listener displayAdapterListener,
-                        DisplayManagerFlags flags) {
+                        DisplayManagerFlags flags,
+                        DisplayNotificationManager displayNotificationManager) {
                     return new LocalDisplayAdapter(syncRoot, context, handler,
-                            displayAdapterListener, flags, new LocalDisplayAdapter.Injector() {
+                            displayAdapterListener, flags,
+                            mMockedDisplayNotificationManager,
+                            new LocalDisplayAdapter.Injector() {
                         @Override
                         public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
                             return mSurfaceControlProxy;
@@ -248,13 +251,15 @@
         @Override
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                 Handler handler, DisplayAdapter.Listener displayAdapterListener,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags,
+                DisplayNotificationManager displayNotificationManager) {
             return new LocalDisplayAdapter(
                     syncRoot,
                     context,
                     handler,
                     displayAdapterListener,
                     flags,
+                    mMockedDisplayNotificationManager,
                     new LocalDisplayAdapter.Injector() {
                         @Override
                         public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
@@ -288,6 +293,7 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
+    @Mock DisplayNotificationManager mMockedDisplayNotificationManager;
     @Mock IMediaProjectionManager mMockProjectionService;
     @Mock IVirtualDeviceManager mIVirtualDeviceManager;
     @Mock InputManagerInternal mMockInputManagerInternal;
@@ -305,7 +311,6 @@
     @Mock SensorManager mSensorManager;
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
     @Mock PackageManagerInternal mMockPackageManagerInternal;
-    @Mock UserManagerInternal mMockUserManagerInternal;
 
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -329,8 +334,6 @@
                 VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
-        LocalServices.removeServiceForTest(UserManagerInternal.class);
-        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
         // TODO: b/287945043
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d2..47521d1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -74,6 +74,7 @@
 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.feature.DisplayManagerFlags;
@@ -1314,6 +1315,54 @@
     }
 
     @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
@@ -1637,13 +1686,20 @@
                 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,
-                flags));
+                clamperController, flags));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1662,8 +1718,8 @@
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
-                brightnessMappingStrategy, injector, config);
+                screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+                hbmMetadata, brightnessMappingStrategy, injector, config);
     }
 
     /**
@@ -1682,6 +1738,7 @@
         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;
@@ -1695,6 +1752,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController hbmController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 HighBrightnessModeMetadata hbmMetadata,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmController = hbmController;
             this.hdrClamper = hdrClamper;
+            this.clamperController = clamperController;
             this.hbmMetadata = hbmMetadata;
             this.brightnessMappingStrategy = brightnessMappingStrategy;
             this.injector = injector;
@@ -1728,6 +1787,8 @@
 
         private final HdrClamper mHdrClamper;
 
+        private final BrightnessClamperController mClamperController;
+
         private final DisplayManagerFlags mFlags;
 
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
@@ -1748,6 +1810,7 @@
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
             mHdrClamper = hdrClamper;
+            mClamperController = clamperController;
             mFlags = flags;
         }
 
@@ -1864,6 +1927,14 @@
         }
 
         @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/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 147e8f2..a77a958 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -60,6 +61,7 @@
 import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
@@ -113,6 +115,8 @@
     @Mock
     private LogicalLight mMockedBacklight;
     @Mock
+    private DisplayNotificationManager mMockedDisplayNotificationManager;
+    @Mock
     private DisplayManagerFlags mFlags;
 
     private Handler mHandler;
@@ -148,7 +152,7 @@
         mInjector = new Injector();
         when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
         mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
-                mListener, mFlags, mInjector);
+                mListener, mFlags, mMockedDisplayNotificationManager, mInjector);
         spyOn(mAdapter);
         doReturn(mMockedContext).when(mAdapter).getOverlayContext();
 
@@ -425,7 +429,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -447,7 +451,7 @@
         SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
         mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
                 new Display.Mode(displayMode2.width, displayMode2.height,
-                        displayMode2.refreshRate));
+                        displayMode2.peakRefreshRate));
         updateAvailableDisplays();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
@@ -481,7 +485,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -696,7 +700,7 @@
 
         // Turn off.
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
-                0);
+                0, null);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         assertThat(mListener.changedDisplays.size()).isEqualTo(1);
         mListener.changedDisplays.clear();
@@ -943,7 +947,7 @@
         // Set the user preferred display mode
         mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
                 new Display.Mode(
-                        displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+                        displayMode3.width, displayMode3.height, displayMode3.peakRefreshRate));
         updateAvailableDisplays();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         displayDeviceInfo = mListener.addedDisplays.get(
@@ -988,6 +992,52 @@
     }
 
     @Test
+    public void testGetAndSetDisplayModesDisambiguatesByVsyncRate() throws Exception {
+        SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f, 120f);
+        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 60f, 60f);
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 0);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode1)).isTrue();
+        assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), displayMode1))
+                .isTrue();
+
+        display.dynamicInfo.preferredBootDisplayMode = 1;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice changedDisplayDevice = mListener.changedDisplays.get(0);
+        changedDisplayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = changedDisplayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+        assertThat(
+                matches(changedDisplayDevice.getSystemPreferredDisplayModeLocked(), displayMode2))
+                .isTrue();
+    }
+
+    @Test
     public void testHdrSdrRatio_notifiesOnChange() throws Exception {
         FakeDisplay display = new FakeDisplay(PORT_A);
         setUpDisplay(display);
@@ -1000,7 +1050,7 @@
         // Turn on / initialize
         assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1009,7 +1059,7 @@
 
         // HDR time!
         Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
-                0);
+                0, null);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         // Display state didn't change, no listeners should have happened
         assertThat(mListener.changedDisplays.size()).isEqualTo(0);
@@ -1040,7 +1090,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1067,7 +1117,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1092,7 +1142,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1115,7 +1165,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1142,9 +1192,9 @@
             Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
                     supportedState, 0, 0, mDisplayOffloadSession);
             changeStateRunnable.run();
-
-            verify(mDisplayOffloader).startOffload();
         }
+
+        verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
     }
 
     @Test
@@ -1226,7 +1276,7 @@
     private void assertModeIsSupported(Display.Mode[] supportedModes,
             SurfaceControl.DisplayMode mode) {
         assertThat(Arrays.stream(supportedModes).anyMatch(
-                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+                x -> x.matches(mode.width, mode.height, mode.peakRefreshRate))).isTrue();
     }
 
     private void assertModeIsSupported(Display.Mode[] supportedModes,
@@ -1240,7 +1290,7 @@
                 + Arrays.toString(supportedModes);
         Truth.assertWithMessage(message)
             .that(Arrays.stream(supportedModes)
-                .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+                .anyMatch(x -> x.matches(mode.width, mode.height, mode.peakRefreshRate)
                         && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
                 .isTrue();
     }
@@ -1328,16 +1378,28 @@
 
     private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
             float refreshRate) {
-        return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+        return createFakeDisplayMode(id, width, height, refreshRate, refreshRate);
     }
 
     private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
-            float refreshRate, int group) {
+                                                                   float refreshRate,
+                                                                    float vsyncRate) {
+        return createFakeDisplayMode(id, width, height, refreshRate, vsyncRate, /* group */ 0);
+    }
+
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+                                                                    float refreshRate, int group) {
+        return createFakeDisplayMode(id, width, height, refreshRate, refreshRate, group);
+    }
+
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+            float refreshRate, float vsyncRate, int group) {
         final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
         mode.id = id;
         mode.width = width;
         mode.height = height;
-        mode.refreshRate = refreshRate;
+        mode.peakRefreshRate = refreshRate;
+        mode.vsyncRate = vsyncRate;
         mode.xDpi = 100;
         mode.yDpi = 100;
         mode.group = group;
@@ -1440,6 +1502,9 @@
 
     private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
         return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
-                && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+                && Float.floatToIntBits(a.getRefreshRate())
+                        == Float.floatToIntBits(b.peakRefreshRate)
+                && Float.floatToIntBits(a.getVsyncRate())
+                        == Float.floatToIntBits(b.vsyncRate);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 065dd1f..8b13018 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -720,13 +720,15 @@
                 mIdProducer, POSITION_UNKNOWN,
                 /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ "concurrent");
         layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
                 mIdProducer, POSITION_UNKNOWN,
                 /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ "concurrent");
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
 
         layout = new Layout();
@@ -927,7 +929,7 @@
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
                 mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */null);
+                /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null);
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
 
         when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
@@ -986,7 +988,7 @@
         layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
                 Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null);
     }
 
     private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 22d2622..ff2b1f4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -35,6 +35,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.testutils.TestHandler;
 
 import org.junit.Before;
@@ -63,12 +64,13 @@
     @Mock
     private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
     @Mock
+    private DisplayManagerFlags mFlags;
+    @Mock
     private BrightnessModifier mMockModifier;
     @Mock
     private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
     @Mock
     private DeviceConfig.Properties mMockProperties;
-
     private BrightnessClamperController mClamperController;
     private TestInjector mTestInjector;
 
@@ -135,8 +137,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(false);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -148,6 +152,7 @@
         assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(0,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertEquals(initialSlowChange, state.isSlowChange());
     }
 
@@ -156,8 +161,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -169,6 +176,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertFalse(state.isSlowChange());
     }
 
@@ -177,8 +185,10 @@
         float initialBrightness = 0.6f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.8f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -190,6 +200,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertFalse(state.isSlowChange());
     }
 
@@ -198,8 +209,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -214,12 +227,13 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertEquals(initialSlowChange, state.isSlowChange());
     }
 
     private BrightnessClamperController createBrightnessClamperController() {
         return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
-                mMockDisplayDeviceData, mMockContext);
+                mMockDisplayDeviceData, mMockContext, mFlags);
     }
 
     private class TestInjector extends BrightnessClamperController.Injector {
@@ -247,7 +261,8 @@
         List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
                 Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
-                BrightnessClamperController.DisplayDeviceData data) {
+                BrightnessClamperController.DisplayDeviceData data,
+                DisplayManagerFlags flags) {
             mCapturedChangeListener = clamperChangeListener;
             return mClampers;
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
new file mode 100644
index 0000000..b3f33ad
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerClamperTest {
+    private static final String TAG = "BrightnessPowerClamperTest";
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private static final String DISPLAY_ID = "displayId";
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+    private TestPmicMonitor mPmicMonitor;
+    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+            new FakeDeviceConfigInterface();
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private BrightnessPowerClamper mClamper;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler,
+                mMockClamperChangeListener, new TestPowerData());
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testTypeIsPower() {
+        assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
+    }
+
+    @Test
+    public void testNoThrottlingData() {
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testPowerThrottlingNoOngoingAnimation() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+        mPmicMonitor.setAvgPowerConsumed(200f);
+        float expectedBrightness = 0.5f;
+        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+        mTestHandler.flush();
+        // Assume current brightness as max, as there is no throttling.
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+        mPmicMonitor.setAvgPowerConsumed(100f);
+        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+        mTestHandler.flush();
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testPowerThrottlingWithOngoingAnimation() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+        mTestHandler.flush();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+        mPmicMonitor.setAvgPowerConsumed(200f);
+        float expectedBrightness = 0.5f;
+        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+        mTestHandler.flush();
+        // Assume current brightness as max, as there is no throttling.
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+        mPmicMonitor.setAvgPowerConsumed(100f);
+        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+        mTestHandler.flush();
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
+
+        mPmicMonitor.setAvgPowerConsumed(200f);
+        float expectedBrightness = 0.5f;
+        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+        mTestHandler.flush();
+
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+
+        mPmicMonitor.setAvgPowerConsumed(100f);
+        // No cap applied for Temperature.THROTTLING_NONE
+        expectedBrightness = PowerManager.BRIGHTNESS_MAX;
+        mTestHandler.flush();
+
+        // clamper should not be active anymore.
+        assertFalse(mClamper.isActive());
+        // Assume current brightness as max, as there is no throttling.
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+
+    private static class TestPmicMonitor extends PmicMonitor {
+        private Temperature mCurrentTemperature;
+        private final PowerChangeListener mListener;
+        TestPmicMonitor(PowerChangeListener listener, int pollingTime) {
+            super(listener, pollingTime);
+            mListener = listener;
+        }
+        public void setAvgPowerConsumed(float power) {
+            int status = mCurrentTemperature.getStatus();
+            mListener.onChanged(power, status);
+        }
+        public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+            mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+        }
+    }
+
+    private class TestInjector extends BrightnessPowerClamper.Injector {
+        @Override
+        TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
+                int pollingTime) {
+            mPmicMonitor = new TestPmicMonitor(listener, pollingTime);
+            return mPmicMonitor;
+        }
+
+        @Override
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+        }
+    }
+
+    private static class TestPowerData implements BrightnessPowerClamper.PowerData {
+
+        private final String mUniqueDisplayId;
+        private final String mDataId;
+        private final PowerThrottlingData mData;
+        private final PowerThrottlingConfigData mConfigData;
+
+        private TestPowerData() {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+        }
+
+        private TestPowerData(List<ThrottlingLevel> data) {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+        }
+
+        private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+            mUniqueDisplayId = uniqueDisplayId;
+            mDataId = dataId;
+            mData = PowerThrottlingData.create(data);
+            mConfigData = new PowerThrottlingConfigData(0.1f, 10);
+        }
+
+        @NonNull
+        @Override
+        public String getUniqueDisplayId() {
+            return mUniqueDisplayId;
+        }
+
+        @NonNull
+        @Override
+        public String getPowerThrottlingDataId() {
+            return mDataId;
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingData getPowerThrottlingData() {
+            return mData;
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+            return mConfigData;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index ee187ba..8d8274c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -56,9 +56,9 @@
     private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
             Map.of(500f, 0.6f),
             /* brightnessIncreaseDebounceMillis= */ 1000,
-            /* brightnessIncreaseDurationMillis= */ 2000,
+            /* screenBrightnessRampIncrease= */ 0.02f,
             /* brightnessDecreaseDebounceMillis= */ 3000,
-            /* brightnessDecreaseDurationMillis= */4000
+            /* screenBrightnessRampDecrease= */0.04f
     );
 
     private static final int WIDTH = 600;
@@ -152,8 +152,7 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
-        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.04, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -181,8 +180,7 @@
         mClock.fastForward(1000);
         mTestHandler.timeAdvance();
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
-        assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.02f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -209,8 +207,7 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
-        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.04f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index c7c09b5..ec27f9d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -44,12 +44,12 @@
 import android.test.mock.MockContentResolver;
 import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
 import com.android.server.SystemService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -57,6 +57,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -90,9 +91,13 @@
         ColorDisplayManager.COLOR_MODE_BOOSTED,
     };
 
+    @Rule
+    public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
     @Before
     public void setUp() {
-        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mContext = Mockito.spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).getApplicationContext();
 
         final Resources res = Mockito.spy(mContext.getResources());
@@ -112,43 +117,36 @@
         doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
 
         mTwilightManager = new MockTwilightManager();
-        LocalServices.addService(TwilightManager.class, mTwilightManager);
+        mLocalServiceKeeperRule.overrideLocalService(TwilightManager.class, mTwilightManager);
 
         mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
         doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
-        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+        mLocalServiceKeeperRule.overrideLocalService(
+                DisplayTransformManager.class, mDisplayTransformManager);
 
         mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+        mLocalServiceKeeperRule.overrideLocalService(
+                DisplayManagerInternal.class, mDisplayManagerInternal);
 
         mCds = new ColorDisplayService(mContext);
         mBinderService = mCds.new BinderService();
-        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+        mLocalServiceKeeperRule.overrideLocalService(
+                ColorDisplayService.ColorDisplayServiceInternal.class,
                 mCds.new ColorDisplayServiceInternal());
     }
 
     @After
     public void tearDown() {
-        /*
-         * Wait for internal {@link Handler} to finish processing pending messages, so that test
-         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
-         */
-        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
+        // synchronously cancel all animations
+        mCds.mHandler.runWithScissors(() -> mCds.cancelAllAnimators(), /* timeout */ 1000);
         mCds = null;
 
-        LocalServices.removeServiceForTest(TwilightManager.class);
         mTwilightManager = null;
 
-        LocalServices.removeServiceForTest(DisplayTransformManager.class);
-
         mUserId = UserHandle.USER_NULL;
         mContext = null;
 
         FakeSettingsProvider.clearSettingsProvider();
-
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
     }
 
     @Test
@@ -1249,10 +1247,10 @@
     private void startService() {
         Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-            mCds.onUserChanged(mUserId);
-        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED));
+        // onUserChanged cancels running animations, and should be called in handler thread
+        mCds.mHandler.runWithScissors(() -> mCds.onUserChanged(mUserId), 1000);
     }
 
     /**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index c280349..79222c0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -23,6 +23,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -37,6 +38,7 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.R;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -54,6 +56,8 @@
     private Resources mMockedResources;
     @Mock
     private DisplayManagerInternal mDisplayManagerInternal;
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlagsMock;
 
     private MockitoSession mSession;
     private Resources mResources;
@@ -63,10 +67,6 @@
     @Before
     public void setUp() {
         DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
-        mDisplayWhiteBalanceTintController =
-                new DisplayWhiteBalanceTintController(displayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
 
         mSession = ExtendedMockito.mockitoSession()
                 .initMocks(this)
@@ -74,6 +74,13 @@
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
+        mDisplayWhiteBalanceTintController =
+                new DisplayWhiteBalanceTintController(displayManagerInternal,
+                        mDisplayManagerFlagsMock);
+        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+
+
         mResources = InstrumentationRegistry.getContext().getResources();
         // These Resources are common to all tests.
         doReturn(4000)
@@ -360,9 +367,47 @@
                 1e-6f /* tolerance */);
     }
 
+    @Test
+    public void testDisplayWhiteBalance_TransitionTimes() {
+        when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(false);
+        setUpTransitionTimes();
+        setUpTintController();
+
+        assertEquals(30L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true));
+        assertEquals(30L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false));
+    }
+
+    @Test
+    public void testDisplayWhiteBalance_TransitionTimesDirectional() {
+        when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(true);
+        setUpTransitionTimes();
+        setUpTintController();
+
+        assertEquals(400L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true));
+        assertEquals(5000L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false));
+    }
+
+
+    private void setUpTransitionTimes() {
+        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        when(mMockedResources.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTime)).thenReturn(30);
+        when(mMockedResources.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTimeIncrease)).thenReturn(400);
+        when(mMockedResources.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTimeDecrease)).thenReturn(5000);
+
+    }
+
     private void setUpTintController() {
         mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
-                mDisplayManagerInternal);
+                mDisplayManagerInternal, mDisplayManagerFlagsMock);
         mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
         mDisplayWhiteBalanceTintController.setActivated(true);
     }
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 c4f72b3..6a95d5c 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
@@ -102,6 +102,9 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.testutils.FakeDeviceConfigInterface;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -121,26 +124,28 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
 @SmallTest
 @RunWith(JUnitParamsRunner.class)
 public class DisplayModeDirectorTest {
     public static Collection<Object[]> getAppRequestedSizeTestCases() {
         var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
-                {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY,
-                        DEFAULT_MODE_75.getRefreshRate(), Map.of()},
-                {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY,
-                        APP_MODE_HIGH_90.getRefreshRate(),
-                        Map.of(
+                {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of()},
+                {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))},
-                {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
-                        Map.of(
+                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
@@ -149,9 +154,10 @@
                                 Vote.PRIORITY_LOW_POWER_MODE,
                                 Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
                                         LIMIT_MODE_70.getPhysicalHeight()))},
-                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
-                        LIMIT_MODE_70.getRefreshRate(),
-                        Map.of(
+                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
@@ -160,9 +166,10 @@
                                 Vote.PRIORITY_LOW_POWER_MODE,
                                 Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
                                         LIMIT_MODE_70.getPhysicalHeight()))},
-                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
-                        LIMIT_MODE_70.getRefreshRate(),
-                        Map.of(
+                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
@@ -173,10 +180,12 @@
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
                                     LIMIT_MODE_70.getPhysicalHeight(),
-                                    0, Float.POSITIVE_INFINITY)), false},
-                {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(),
-                        APP_MODE_65.getRefreshRate(),
-                        Map.of(
+                                    0, Float.POSITIVE_INFINITY)),
+                        /*displayResolutionRangeVotingEnabled*/ false},
+                {/*expectedBaseModeId*/ APP_MODE_65.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
@@ -187,7 +196,40 @@
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
                                     LIMIT_MODE_70.getPhysicalHeight(),
-                                    0, Float.POSITIVE_INFINITY)), true}});
+                                    0, Float.POSITIVE_INFINITY)),
+                        /*displayResolutionRangeVotingEnabled*/ true},
+                {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+                                        APP_MODE_HIGH_90.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSizeAndPhysicalRefreshRatesRange(
+                                    0, 0,
+                                    LIMIT_MODE_70.getPhysicalWidth(),
+                                    LIMIT_MODE_70.getPhysicalHeight(),
+                                    0, APP_MODE_65.getRefreshRate())),
+                        /*displayResolutionRangeVotingEnabled*/ false},
+                {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65
+                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+                                        APP_MODE_HIGH_90.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSizeAndPhysicalRefreshRatesRange(
+                                    0, 0,
+                                    LIMIT_MODE_70.getPhysicalWidth(),
+                                    LIMIT_MODE_70.getPhysicalHeight(),
+                                    0, APP_MODE_65.getRefreshRate())),
+                        /*displayResolutionRangeVotingEnabled*/ true}});
 
         final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
 
@@ -218,6 +260,8 @@
     private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
 
+    private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode(
+            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60);
     private static final Display.Mode APP_MODE_65 = new Display.Mode(
             /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65);
     private static final Display.Mode LIMIT_MODE_70 = new Display.Mode(
@@ -227,8 +271,7 @@
     private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode(
             /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90);
     private static final Display.Mode[] TEST_MODES = new Display.Mode[] {
-        new Display.Mode(
-            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60),
+        DEFAULT_MODE_60,
         APP_MODE_65,
         LIMIT_MODE_70,
         DEFAULT_MODE_75,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
new file mode 100644
index 0000000..d5a92cb
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ConnectedDisplayUsbErrorsDetector}
+ * Run: atest ConnectedDisplayUsbErrorsDetectorTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class ConnectedDisplayUsbErrorsDetectorTest {
+    @Mock
+    private Injector mMockedInjector;
+    @Mock
+    private UsbManager mMockedUsbManager;
+    @Mock
+    private DisplayManagerFlags mMockedFlags;
+    @Mock
+    private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener;
+
+    /** Setup tests. */
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNoErrorTypes(
+            @TestParameter final boolean isUsbManagerAvailable,
+            @TestParameter final boolean isUsbErrorsNotificationEnabled) {
+        // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure
+        assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled);
+        var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled);
+        // None of these should trigger an error now.
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+        verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any());
+        verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+        verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+    }
+
+    @Test
+    public void testErrorOnUsbCableNotCapableDp() {
+        var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+                /*isUsbErrorsNotificationEnabled=*/ true);
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+        verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+        verify(mMockedListener).onCableNotCapableDisplayPort();
+        verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+    }
+
+    @Test
+    public void testErrorOnDpLinkTrainingFailure() {
+        var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+                /*isUsbErrorsNotificationEnabled=*/ true);
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+        verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+        verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+        verify(mMockedListener).onDisplayPortLinkTrainingFailure();
+    }
+
+    private ConnectedDisplayUsbErrorsDetector createErrorsDetector(
+            final boolean isUsbManagerAvailable,
+            final boolean isConnectedDisplayUsbErrorsNotificationEnabled) {
+        when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled())
+                .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled);
+        when(mMockedInjector.getUsbManager()).thenReturn(
+                (isUsbManagerAvailable) ? mMockedUsbManager : null);
+        var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags,
+                ApplicationProvider.getApplicationContext(), mMockedInjector);
+        detector.registerListener(mMockedListener);
+        return detector;
+    }
+
+    private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() {
+        return new DisplayPortAltModeInfo(
+                    DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+                    DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0);
+    }
+
+    private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() {
+        return new DisplayPortAltModeInfo(
+                    DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+                    DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false,
+                    LINK_TRAINING_STATUS_FAILURE);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
new file mode 100644
index 0000000..1d2034b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
@@ -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.server.display.notifications;
+
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.DisplayNotificationManager.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link DisplayNotificationManager}
+ * Run: atest DisplayNotificationManagerTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class DisplayNotificationManagerTest {
+    @Mock
+    private Injector mMockedInjector;
+    @Mock
+    private NotificationManager mMockedNotificationManager;
+    @Mock
+    private DisplayManagerFlags mMockedFlags;
+    @Captor
+    private ArgumentCaptor<String> mNotifyTagCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mNotifyNoteIdCaptor;
+    @Captor
+    private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor;
+
+    /** Setup tests. */
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNotificationOnHotplugConnectionError() {
+        var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+                /*isErrorHandlingEnabled=*/ true);
+        dnm.onHotplugConnectionError();
+        assertExpectedNotification();
+    }
+
+    @Test
+    public void testNotificationOnDisplayPortLinkTrainingFailure() {
+        var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+                /*isErrorHandlingEnabled=*/ true);
+        dnm.onDisplayPortLinkTrainingFailure();
+        assertExpectedNotification();
+    }
+
+    @Test
+    public void testNotificationOnCableNotCapableDisplayPort() {
+        var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+                /*isErrorHandlingEnabled=*/ true);
+        dnm.onCableNotCapableDisplayPort();
+        assertExpectedNotification();
+    }
+
+    @Test
+    public void testNoErrorNotification(
+            @TestParameter final boolean isNotificationManagerAvailable,
+            @TestParameter final boolean isErrorHandlingEnabled) {
+        /* This case is tested by #testNotificationOnHotplugConnectionError,
+            #testNotificationOnDisplayPortLinkTrainingFailure,
+            #testNotificationOnCableNotCapableDisplayPort */
+        assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled);
+        var dnm = createDisplayNotificationManager(isNotificationManagerAvailable,
+                isErrorHandlingEnabled);
+        // None of these methods should trigger a notification now.
+        dnm.onHotplugConnectionError();
+        dnm.onDisplayPortLinkTrainingFailure();
+        dnm.onCableNotCapableDisplayPort();
+        verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any());
+    }
+
+    private DisplayNotificationManager createDisplayNotificationManager(
+            final boolean isNotificationManagerAvailable,
+            final boolean isErrorHandlingEnabled) {
+        when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(
+                isErrorHandlingEnabled);
+        when(mMockedInjector.getNotificationManager()).thenReturn(
+                (isNotificationManagerAvailable) ? mMockedNotificationManager : null);
+        // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest
+        when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null);
+        final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags,
+                ApplicationProvider.getApplicationContext(), mMockedInjector);
+        displayNotificationManager.onBootCompleted();
+        return displayNotificationManager;
+    }
+
+    private void assertExpectedNotification() {
+        verify(mMockedNotificationManager).notify(
+                mNotifyTagCaptor.capture(),
+                mNotifyNoteIdCaptor.capture(),
+                mNotifyAsUserNotificationCaptor.capture());
+        assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager");
+        assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1);
+        final var notification = mNotifyAsUserNotificationCaptor.getValue();
+        assertThat(notification.getChannelId()).isEqualTo("ALERTS");
+        assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR);
+        assertThat(notification.visibility).isEqualTo(Notification.VISIBILITY_PUBLIC);
+        assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0);
+        assertThat(notification.when).isEqualTo(0);
+        assertThat(notification.getTimeoutAfter()).isEqualTo(30000L);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 5b51963..21e3b34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -62,8 +62,6 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
@@ -75,7 +73,6 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
@@ -85,7 +82,6 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
-import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
 import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -799,47 +795,6 @@
     }
 
     @Test
-    public void updatingExactAlarmDenyList() {
-        ArraySet<String> denyListed = new ArraySet<>(new String[]{
-                "com.example.package1",
-                "com.example.package2",
-                "com.example.package3",
-        });
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
-                "com.example.package1,com.example.package2,com.example.package3");
-        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-
-        denyListed = new ArraySet<>(new String[]{
-                "com.example.package1",
-                "com.example.package4",
-        });
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
-                "com.example.package1,com.example.package4");
-        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
-        assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size());
-    }
-
-    @Test
-    public void exactAlarmDenyListMaxSize() {
-        final ArraySet<String> expectedSet = new ArraySet<>();
-        final StringBuilder sb = new StringBuilder("package1");
-        expectedSet.add("package1");
-        for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) {
-            sb.append(",package");
-            sb.append(i);
-            if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) {
-                expectedSet.add("package" + i);
-            }
-        }
-        assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size());
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString());
-        assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST);
-    }
-
-    @Test
     public void positiveWhileIdleQuotas() {
         setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
         assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
@@ -2212,50 +2167,30 @@
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
+    public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
         assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_IGNORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
-        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-    }
-
-    @Test
     public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
@@ -2281,41 +2216,33 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         // No permission, no exemption.
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        // No permission, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Policy permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         mockUseExactAlarmState(false);
 
         // User permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
-        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        // User permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
         doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Both permissions and exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
     }
@@ -2514,17 +2441,12 @@
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
-    private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
+    private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) {
         String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
         when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
                 .thenReturn(requesters);
         mService.refreshExactAlarmCandidates();
 
-        if (denyList) {
-            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE);
-        } else {
-            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
-        }
         when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(mode);
     }
@@ -2556,7 +2478,7 @@
     public void alarmClockBinderCallWithoutPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2630,7 +2552,7 @@
     public void exactBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2650,7 +2572,7 @@
     public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2676,7 +2598,7 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(false, MODE_ERRORED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2700,7 +2622,7 @@
     public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2726,7 +2648,7 @@
     public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2836,7 +2758,7 @@
     public void binderCallWithUserAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
         when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
 
@@ -3052,135 +2974,11 @@
     }
 
     @Test
-    public void denyListChanged() {
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT);
-
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
-
-        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(),
-                anyLong());
-
-        final List<Message> messages = messageCaptor.getAllValues();
-        for (final Message msg : messages) {
-            assertTrue("Unwanted message sent to handler: " + msg.what,
-                    msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED
-                            || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED);
-            mService.mHandler.handleMessage(msg);
-        }
-
-        ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"});
-        verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true));
-
-        ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"});
-        verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false));
-    }
-
-    @Test
-    public void permissionGrantedDueToDenyList() {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        final String[] packages = {"example.package.1", "example.package.2"};
-
-        final int appId1 = 232;
-        final int appId2 = 431;
-
-        final int userId1 = 42;
-        final int userId2 = 53;
-
-        registerAppIds(packages, new Integer[]{appId1, appId2});
-
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
-        when(mPermissionManagerInternal.getAppOpPermissionPackages(
-                SCHEDULE_EXACT_ALARM)).thenReturn(packages);
-        mService.refreshExactAlarmCandidates();
-
-        final long allowListDuration = 53442;
-        when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(
-                allowListDuration);
-
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
-        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
-
-        // No permission revoked.
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(),
-                anyBoolean());
-
-        // Permission got granted only for (appId1, userId2).
-        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class);
-
-        verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(),
-                isNull(), bundleCaptor.capture());
-
-        assertEquals(userId2, userCaptor.getValue().getIdentifier());
-
-        // Validate the intent.
-        assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
-                intentCaptor.getValue().getAction());
-        assertEquals(packages[0], intentCaptor.getValue().getPackage());
-
-        // Validate the options.
-        final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
-        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
-                bOptions.getTemporaryAppAllowlistType());
-        assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration());
-        assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
-                bOptions.getTemporaryAppAllowlistReasonCode());
-    }
-
-    @Test
-    public void permissionRevokedDueToDenyList() {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        final String[] packages = {"example.package.1", "example.package.2"};
-
-        final int appId1 = 232;
-        final int appId2 = 431;
-
-        final int userId1 = 42;
-        final int userId2 = 53;
-
-        registerAppIds(packages, new Integer[]{appId1, appId2});
-
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
-        when(mPermissionManagerInternal.getAppOpPermissionPackages(
-                SCHEDULE_EXACT_ALARM)).thenReturn(packages);
-        mService.refreshExactAlarmCandidates();
-
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
-        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true);
-
-        // Permission got revoked only for (appId1, userId2)
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));
-
-        verify(mService).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
-    }
-
-    @Test
     public void opChangedPermissionRevoked() throws Exception {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
         assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3193,20 +2991,7 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-
-        mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
-
-        verify(mService.mHandler, never()).sendMessageAtTime(
-                argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
-    }
-
-    @Test
-    public void opChangedNoPermissionChangeDueToDenyList() throws Exception {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
-
-        mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
@@ -3222,7 +3007,7 @@
         when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3482,7 +3267,7 @@
                 .putExtra(Intent.EXTRA_REPLACING, true);
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3490,7 +3275,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3498,7 +3283,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3653,16 +3438,16 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
 
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
+        assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
-
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
@@ -3671,11 +3456,11 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mockScheduleExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
         mockScheduleExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 596a3f3..b3605cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -280,7 +280,9 @@
                 0, 0);
 
         // Sleep until timeout should have triggered
-        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+        if (wedge) {
+            SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+        }
 
         return app;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 410ae35..367e14b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -228,7 +228,7 @@
         LocalServices.removeServiceForTest(AlarmManagerInternal.class);
         LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
-        doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+        doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
         doAnswer((invocation) -> {
             return getUidForPackage(invocation.getArgument(0));
         }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
@@ -1014,8 +1014,9 @@
                     eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
 
             // Confirm that we unstopped manifest receivers
-            verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
-                    eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+            verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed(
+                    eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM),
+                    eq(callerApp.info.packageName), any());
         }
 
         // Confirm that we've reported expected usage events
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 76b41b7..37fe8d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2617,6 +2617,31 @@
         assertTrue(CACHED_APP_MAX_ADJ >= app3.mState.getSetAdj());
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoOne_AboveClient_NotStarted() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+        doReturn(app).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+        // Start binding to a service that isn't running yet.
+        ServiceRecord sr = makeServiceRecord(app);
+        sr.app = null;
+        bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+
+        // Since sr.app is null, this service cannot be in the same process as the
+        // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
+        app.mServices.updateHasAboveClientLocked();
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        assertTrue(app.mServices.hasAboveClient());
+        assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+    }
+
     private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
             String packageName, boolean hasShownUi) {
         long now = SystemClock.uptimeMillis();
@@ -2629,7 +2654,7 @@
                 PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT,
                 0, 0, false, false, false, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE,
                 false, false, false, hasShownUi, false, false, false, false, false, false, null,
-                0, 0, 0, true, 0, null, false);
+                0, Long.MIN_VALUE, Long.MIN_VALUE, true, 0, null, false);
     }
 
     private ProcessRecord makeProcessRecord(ActivityManagerService service, int pid, int uid,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
index 1ce79a5..05ac5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.display;
 
-import static android.os.Process.INVALID_UID;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -35,7 +33,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -55,7 +53,10 @@
     @Mock
     private PackageManagerInternal mMockPackageManagerInternal;
     @Mock
-    private UserManagerInternal mMockUserManagerInternal;
+    private PackageStateInternal mMockPkgStateA;
+    @Mock
+    private PackageStateInternal mMockPkgStateB;
+
 
     private SmallAreaDetectionController mSmallAreaDetectionController;
 
@@ -64,29 +65,18 @@
     private static final String PKG_NOT_INSTALLED = "com.not.installed";
     private static final float THRESHOLD_A = 0.05f;
     private static final float THRESHOLD_B = 0.07f;
-    private static final int USER_1 = 110;
-    private static final int USER_2 = 111;
-    private static final int UID_A_1 = 11011111;
-    private static final int UID_A_2 = 11111111;
-    private static final int UID_B_1 = 11022222;
-    private static final int UID_B_2 = 11122222;
+    private static final int APP_ID_A = 11111;
+    private static final int APP_ID_B = 22222;
 
     @Before
     public void setup() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
-        LocalServices.removeServiceForTest(UserManagerInternal.class);
-        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
 
-        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
-        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
-                INVALID_UID);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
-                INVALID_UID);
+        when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA);
+        when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB);
+        when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A);
+        when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B);
 
         mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
                 new ContextWrapper(ApplicationProvider.getApplicationContext()),
@@ -99,9 +89,9 @@
         final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
         mSmallAreaDetectionController.updateAllowlist(property);
 
-        final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
-        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
-        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+        final int[] resultAppIdArray = {APP_ID_A, APP_ID_B};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
                 eq(resultThresholdArray));
     }
 
@@ -110,9 +100,9 @@
         final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
         mSmallAreaDetectionController.updateAllowlist(property);
 
-        final int[] resultUidArray = {UID_B_1, UID_B_2};
-        final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
-        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+        final int[] resultAppIdArray = {APP_ID_B};
+        final float[] resultThresholdArray = {THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
                 eq(resultThresholdArray));
     }
 
@@ -122,9 +112,9 @@
                 PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
         mSmallAreaDetectionController.updateAllowlist(property);
 
-        final int[] resultUidArray = {UID_A_1, UID_A_2};
-        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
-        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+        final int[] resultAppIdArray = {APP_ID_A};
+        final float[] resultThresholdArray = {THRESHOLD_A};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
                 eq(resultThresholdArray));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index bded9b4..809a0e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -919,6 +919,7 @@
             assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
             mSetFlagsRule.disableFlags(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER);
             when(mService.isBatteryCharging()).thenReturn(false);
+            when(mService.isBatteryNotLow()).thenReturn(false);
 
             when(mNetPolicyManagerInternal.getSubscriptionOpportunisticQuota(
                     any(), eq(NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS)))
@@ -938,6 +939,12 @@
             assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
 
             when(mService.isBatteryCharging()).thenReturn(true);
+            assertFalse(controller.isSatisfied(latePrefetch, net, caps, mConstants));
+            // Only relax restrictions when we at least know the estimated download bytes.
+            assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants));
+            assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
+
+            when(mService.isBatteryNotLow()).thenReturn(true);
             assertTrue(controller.isSatisfied(latePrefetch, net, caps, mConstants));
             // Only relax restrictions when we at least know the estimated download bytes.
             assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 7cc01e1..4329b6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -160,6 +160,8 @@
         mPrefetchController = new PrefetchController(mJobSchedulerService);
         mPcConstants = mPrefetchController.getPcConstants();
 
+        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
+
         setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT);
 
         verify(mUsageStatsManagerInternal)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 2f6859c..be33b1b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -165,8 +165,7 @@
             null
         }
         whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable(), nullable(), nullable(), nullable())) {
+                nullable(), nullable(), nullable())) {
             val name: String = getArgument(0)
             val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
                     ?: return@whenever null
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 eb50556..e7f1d16e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertThrows;
 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.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doReturn;
@@ -34,7 +35,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -43,6 +46,8 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
 import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -96,6 +101,8 @@
     @Mock
     private LauncherApps mLauncherApps;
     @Mock
+    private ActivityManager mActivityManager;
+    @Mock
     private PackageManager mPackageManager;
     @Mock
     private PackageInstallerService mInstallerService;
@@ -121,6 +128,8 @@
 
     private PackageSetting mPackageSetting;
 
+    private PackageManagerService mPackageManagerService;
+
     private PackageArchiver mArchiveManager;
 
     @Before
@@ -129,7 +138,7 @@
         rule.system().stageNominalSystemState();
         when(rule.mocks().getInjector().getPackageInstallerService()).thenReturn(
                 mInstallerService);
-        PackageManagerService pm = spy(new PackageManagerService(rule.mocks().getInjector(),
+        mPackageManagerService = spy(new PackageManagerService(rule.mocks().getInjector(),
                 /* factoryTest= */false,
                 MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
                 /* isEngBuild= */ false,
@@ -153,17 +162,24 @@
         when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
         when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
                 mLauncherActivityInfos);
-        doReturn(mComputer).when(pm).snapshotComputer();
+
+        when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+        when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+
+        doReturn(mComputer).when(mPackageManagerService).snapshotComputer();
         when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
                 Binder.getCallingUid());
 
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
                 mock(Resources.class));
+        doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
+                .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
+                        eq(mUserId));
 
-        mArchiveManager = spy(new PackageArchiver(mContext, pm));
+        mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService));
         doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
-                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+                any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
         doReturn(mIcon).when(mArchiveManager).decodeIcon(
                 any(ArchiveState.ArchiveActivityInfo.class));
     }
@@ -236,6 +252,21 @@
     }
 
     @Test
+    public void archiveApp_installerDoesntSupportUnarchival() {
+        doReturn(new ParceledListSlice<>(List.of()))
+                .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
+                        eq(mUserId));
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                "Installer does not support unarchival");
+    }
+
+    @Test
     public void archiveApp_noMainActivities() {
         when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
                 List.of());
@@ -253,7 +284,7 @@
     public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
         IOException e = new IOException("IO");
         doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
-                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+                any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
 
         mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
@@ -427,6 +458,7 @@
         for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
             ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
                     mainActivity.getLabel().toString(),
+                    mainActivity.getComponentName(),
                     ICON_PATH, null);
             activityInfos.add(activityInfo);
         }
@@ -437,9 +469,11 @@
         ActivityInfo activityInfo = mock(ActivityInfo.class);
         LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
         when(activity1.getLabel()).thenReturn("activity1");
+        when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));
         when(activity1.getActivityInfo()).thenReturn(activityInfo);
         LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
         when(activity2.getLabel()).thenReturn("activity2");
+        when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));
         when(activity2.getActivityInfo()).thenReturn(activityInfo);
         return List.of(activity1, activity2);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index eefe5af..3dbab13 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -462,7 +462,7 @@
         wallpaper.wallpaperObserver.stopWatching();
 
         spyOn(wallpaper.wallpaperObserver);
-        doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+        doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(false);
         wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
 
         // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
diff --git a/services/tests/powerstatstests/res/xml/power_profile_test.xml b/services/tests/powerstatstests/res/xml/power_profile_test.xml
new file mode 100644
index 0000000..ecd8861
--- /dev/null
+++ b/services/tests/powerstatstests/res/xml/power_profile_test.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<device name="Android">
+    <!-- This is the battery capacity in mAh -->
+    <item name="battery.capacity">3000</item>
+
+    <!-- Power consumption when CPU is suspended -->
+    <item name="cpu.suspend">5</item>
+    <!-- Additional power consumption when CPU is in a kernel idle loop -->
+    <item name="cpu.idle">1.11</item>
+    <!-- Additional power consumption by CPU excluding cluster and core when  running -->
+    <item name="cpu.active">2.55</item>
+
+    <!-- Additional power consumption of CPU policy0 itself when running on related cores -->
+    <item name="cpu.scaling_policy_power.policy0">2.11</item>
+    <!-- Additional power consumption of CPU policy4 itself when running on related cores -->
+    <item name="cpu.scaling_policy_power.policy4">2.22</item>
+
+    <!-- Additional power used by a CPU related to policy3 when running at different
+     speeds. -->
+    <array name="cpu.scaling_step_power.policy0">
+        <value>10</value> <!-- 300 MHz CPU speed -->
+        <value>20</value> <!-- 1000 MHz CPU speed -->
+        <value>30</value> <!-- 1900 MHz CPU speed -->
+    </array>
+    <!-- Additional power used by a CPU related to policy3 when running at different
+         speeds. -->
+    <array name="cpu.scaling_step_power.policy4">
+        <value>25</value> <!-- 300 MHz CPU speed -->
+        <value>35</value> <!-- 1000 MHz CPU speed -->
+        <value>50</value> <!-- 2500 MHz CPU speed -->
+        <value>60</value> <!-- 3000 MHz CPU speed -->
+    </array>
+
+    <!-- Power used by display unit in ambient display mode, including back lighting-->
+    <item name="ambient.on">0.5</item>
+    <!-- Additional power used when screen is turned on at minimum brightness -->
+    <item name="screen.on">100</item>
+    <!-- Additional power used when screen is at maximum brightness, compared to
+         screen at minimum brightness -->
+    <item name="screen.full">800</item>
+
+    <!-- Average power used by the camera flash module when on -->
+    <item name="camera.flashlight">500</item>
+    <!-- Average power use by the camera subsystem for a typical camera
+         application. Intended as a rough estimate for an application running a
+         preview and capturing approximately 10 full-resolution pictures per
+         minute. -->
+    <item name="camera.avg">600</item>
+
+    <!-- Additional power used by the audio hardware, probably due to DSP -->
+    <item name="audio">100.0</item>
+
+    <!-- Additional power used by the video hardware, probably due to DSP -->
+    <item name="video">150.0</item> <!-- ~50mA -->
+
+    <!-- Additional power used when GPS is acquiring a signal -->
+    <item name="gps.on">10</item>
+
+    <!-- Additional power used when cellular radio is transmitting/receiving -->
+    <item name="radio.active">60</item>
+    <!-- Additional power used when cellular radio is paging the tower -->
+    <item name="radio.scanning">3</item>
+    <!-- Additional power used when the cellular radio is on. Multi-value entry,
+         one per signal strength (no signal, weak, moderate, strong) -->
+    <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
+        <value>6</value>       <!-- none -->
+        <value>5</value>       <!-- poor -->
+        <value>4</value>       <!-- moderate -->
+        <value>3</value>       <!-- good -->
+        <value>3</value>       <!-- great -->
+    </array>
+
+    <!-- Cellular modem related values. These constants are deprecated, but still supported and
+         need to be tested -->
+    <item name="modem.controller.sleep">123</item>
+    <item name="modem.controller.idle">456</item>
+    <item name="modem.controller.rx">789</item>
+    <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+        <value>10</value>
+        <value>20</value>
+        <value>30</value>
+        <value>40</value>
+        <value>50</value>
+    </array>
+</device>
\ No newline at end of file
diff --git a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml b/services/tests/powerstatstests/res/xml/power_profile_test_power_brackets.xml
similarity index 100%
rename from core/tests/coretests/res/xml/power_profile_test_power_brackets.xml
rename to services/tests/powerstatstests/res/xml/power_profile_test_power_brackets.xml
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
new file mode 100644
index 0000000..48e2dd7
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MultiStateStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatePowerStatsProcessorTest {
+
+    @Test
+    public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() {
+        AggregatedPowerStatsConfig.PowerComponent config =
+                new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY)
+                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
+
+        AggregatedPowerStatsProcessor.PowerEstimationPlan plan =
+                new AggregatedPowerStatsProcessor.PowerEstimationPlan(config);
+        assertThat(deviceStateEstimatesToStrings(plan))
+                .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]");
+        assertThat(combinedDeviceStatsToStrings(plan))
+                .containsExactly("[[0, 0]]", "[[0, 1]]", "[[1, 0]]", "[[1, 1]]");
+        assertThat(uidStateEstimatesToStrings(plan, config))
+                .containsExactly(
+                        "[[0, 0]]: [ps]: [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4]]",
+                        "[[0, 1]]: [ps]: [[0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4]]",
+                        "[[1, 0]]: [ps]: [[1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 0, 3], [1, 0, 4]]",
+                        "[[1, 1]]: [ps]: [[1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4]]");
+    }
+
+    @Test
+    public void createPowerEstimationPlan_combineDeviceStats() {
+        AggregatedPowerStatsConfig.PowerComponent config =
+                new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY)
+                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                        .trackUidStates(STATE_POWER, STATE_PROCESS_STATE);
+
+        AggregatedPowerStatsProcessor.PowerEstimationPlan plan =
+                new AggregatedPowerStatsProcessor.PowerEstimationPlan(config);
+
+        assertThat(deviceStateEstimatesToStrings(plan))
+                .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]");
+        assertThat(combinedDeviceStatsToStrings(plan))
+                .containsExactly(
+                        "[[0, 0], [0, 1]]",
+                        "[[1, 0], [1, 1]]");
+        assertThat(uidStateEstimatesToStrings(plan, config))
+                .containsExactly(
+                        "[[0, 0], [0, 1]]: [ps]: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]]",
+                        "[[1, 0], [1, 1]]: [ps]: [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]]");
+    }
+
+    private static List<String> deviceStateEstimatesToStrings(
+            AggregatedPowerStatsProcessor.PowerEstimationPlan plan) {
+        return plan.deviceStateEstimations.stream()
+                .map(dse -> dse.stateValues).map(Arrays::toString).toList();
+    }
+
+    private static List<String> combinedDeviceStatsToStrings(
+            AggregatedPowerStatsProcessor.PowerEstimationPlan plan) {
+        return plan.combinedDeviceStateEstimations.stream()
+                .map(cds -> cds.deviceStateEstimations)
+                .map(dses -> dses.stream()
+                        .map(dse -> dse.stateValues).map(Arrays::toString).toList())
+                .map(Object::toString)
+                .toList();
+    }
+
+    private static List<String> uidStateEstimatesToStrings(
+            AggregatedPowerStatsProcessor.PowerEstimationPlan plan,
+            AggregatedPowerStatsConfig.PowerComponent config) {
+        MultiStateStats.States[] uidStateConfig = config.getUidStateConfig();
+        return plan.uidStateEstimates.stream()
+                .map(use ->
+                        use.combinedDeviceStateEstimate.deviceStateEstimations.stream()
+                                .map(dse -> dse.stateValues).map(Arrays::toString).toList()
+                        + ": "
+                        + Arrays.stream(use.states)
+                                .filter(Objects::nonNull)
+                                .map(MultiStateStats.States::getName).toList()
+                        + ": "
+                        + use.proportionalEstimates.stream()
+                                .map(pe -> trackedStatesToString(uidStateConfig, pe.stateValues))
+                                .toList())
+                .toList();
+    }
+
+    private static Object trackedStatesToString(MultiStateStats.States[] states,
+            int[] stateValues) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        boolean first = true;
+        for (int i = 0; i < states.length; i++) {
+            if (!states[i].isTracked()) {
+                continue;
+            }
+
+            if (!first) {
+                sb.append(", ");
+            }
+            first = false;
+            sb.append(stateValues[i]);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
new file mode 100644
index 0000000..2003d04
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.ParseException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatedPowerStatsTest {
+    private static final int TEST_POWER_COMPONENT = 1077;
+    private static final int APP_1 = 27;
+    private static final int APP_2 = 42;
+
+    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+    private PowerStats.Descriptor mPowerComponentDescriptor;
+
+    @Before
+    public void setup() throws ParseException {
+        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+        mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+
+        mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3,
+                PersistableBundle.forPair("speed", "fast"));
+    }
+
+    @Test
+    public void aggregation() {
+        AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+        verifyAggregatedPowerStats(stats);
+    }
+
+    @Test
+    public void xmlPersistence() throws Exception {
+        AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        serializer.setOutput(baos, "UTF-8");
+        stats.writeXml(serializer);
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8");
+        AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser,
+                mAggregatedPowerStatsConfig);
+
+        verifyAggregatedPowerStats(actualStats);
+    }
+
+    private AggregatedPowerStats prepareAggregatePowerStats() {
+        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+        stats.addClockUpdate(1000, 456);
+        stats.setDuration(789);
+
+        stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000);
+        stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+                BatteryConsumer.PROCESS_STATE_CACHED, 2000);
+        stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000);
+
+        PowerStats ps = new PowerStats(mPowerComponentDescriptor);
+        ps.stats[0] = 100;
+        ps.stats[1] = 987;
+
+        ps.uidStats.put(APP_1, new long[]{389, 0, 739});
+        ps.uidStats.put(APP_2, new long[]{278, 314, 628});
+
+        stats.addPowerStats(ps, 3000);
+
+        stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000);
+        stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+        ps.stats[0] = 444;
+        ps.stats[1] = 0;
+
+        ps.uidStats.put(APP_1, new long[]{0, 0, 400});
+        ps.uidStats.put(APP_2, new long[]{100, 200, 300});
+
+        stats.addPowerStats(ps, 5000);
+
+        return stats;
+    }
+
+    private void verifyAggregatedPowerStats(AggregatedPowerStats stats) {
+        PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT)
+                .getPowerStatsDescriptor();
+        assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT);
+        assertThat(descriptor.name).isEqualTo("fan");
+        assertThat(descriptor.statsArrayLength).isEqualTo(2);
+        assertThat(descriptor.uidStatsArrayLength).isEqualTo(3);
+        assertThat(descriptor.extras.getString("speed")).isEqualTo("fast");
+
+        assertThat(getDeviceStats(stats,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+                .isEqualTo(new long[]{322, 987});
+
+        assertThat(getDeviceStats(stats,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+                .isEqualTo(new long[]{222, 0});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_1,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+                .isEqualTo(new long[]{259, 0, 492});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_1,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_CACHED))
+                .isEqualTo(new long[]{129, 0, 446});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_1,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+                BatteryConsumer.PROCESS_STATE_CACHED))
+                .isEqualTo(new long[]{0, 0, 200});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_2,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+                .isEqualTo(new long[]{185, 209, 418});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_2,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isEqualTo(new long[]{142, 204, 359});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_2,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isEqualTo(new long[]{50, 100, 150});
+    }
+
+    private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) {
+        long[] out = new long[states.length];
+        stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states);
+        return out;
+    }
+
+    private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) {
+        long[] out = new long[states.length];
+        stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states);
+        return out;
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 5a2d2e3..663af5d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -42,6 +42,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
@@ -214,6 +215,7 @@
 
     public class TestBatteryStatsImpl extends BatteryStatsImpl {
         public TestBatteryStatsImpl(Context context) {
+            super(Clock.SYSTEM_CLOCK, null);
             mPowerProfile = new PowerProfile(context, true /* forTest */);
 
             SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 77124d0..abb3be7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -113,15 +113,15 @@
     public void constrainedIteration() {
         prepareHistory();
 
-        // Initial time is 3000
+        // Initial time is 1000_000
         assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
-                3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
-        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
-                1003_000L, 2003_000L, 2004_000L);
-        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
-                3_000L, 3_000L, 1003_000L);
+                1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L);
+        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0),
+                2000_000L, 3000_000L, 3001_000L);
+        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L),
+                1000_000L, 1000_000L, 2000_000L);
         assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
-                1003_000L, 2003_000L);
+                2000_000L);
     }
 
     private void prepareHistory() {
@@ -144,7 +144,7 @@
         ArrayList<Long> actualTimestamps = new ArrayList<>();
         while (iterator.hasNext()) {
             BatteryStats.HistoryItem item = iterator.next();
-            actualTimestamps.add(item.currentTime);
+            actualTimestamps.add(item.time);
         }
         assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
     }
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 f22296a..1dd499c 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
@@ -41,6 +41,7 @@
 
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
 
 import org.junit.Before;
@@ -69,6 +70,7 @@
     private File mSystemDir;
     private File mHistoryDir;
     private final MockClock mClock = new MockClock();
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
     private BatteryStatsHistory mHistory;
     private BatteryStats.HistoryPrinter mHistoryPrinter;
     @Mock
@@ -94,7 +96,7 @@
         mClock.realtime = 123;
 
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                mStepDetailsCalculator, mClock, mTracer) {
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {
             @Override
             public boolean readFileToParcel(Parcel out, AtomicFile file) {
                 mReadFiles.add(file.getBaseFile().getName());
@@ -210,7 +212,7 @@
             mClock.realtime = 1000 * i;
             fileList.add(mClock.realtime + ".bh");
 
-            mHistory.startNextFile();
+            mHistory.startNextFile(mClock.realtime);
             createActiveFile(mHistory);
             verifyFileNames(mHistory, fileList);
             verifyActiveFile(mHistory, mClock.realtime + ".bh");
@@ -218,7 +220,7 @@
 
         // create file 32
         mClock.realtime = 1000 * 32;
-        mHistory.startNextFile();
+        mHistory.startNextFile(mClock.realtime);
         createActiveFile(mHistory);
         fileList.add("32000.bh");
         fileList.remove(0);
@@ -229,7 +231,7 @@
 
         // create file 33
         mClock.realtime = 1000 * 33;
-        mHistory.startNextFile();
+        mHistory.startNextFile(mClock.realtime);
         createActiveFile(mHistory);
         // verify file 1 is deleted
         fileList.add("33000.bh");
@@ -240,7 +242,7 @@
 
         // create a new BatteryStatsHistory object, it will pick up existing history files.
         BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                null, mClock, mTracer);
+                null, mClock, mMonotonicClock, mTracer);
         // verify constructor can pick up all files from file system.
         verifyFileNames(history2, fileList);
         verifyActiveFile(history2, "33000.bh");
@@ -262,7 +264,7 @@
         // create file 1.
         mClock.realtime = 2345678;
 
-        history2.startNextFile();
+        history2.startNextFile(mClock.realtime);
         createActiveFile(history2);
         verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
         verifyActiveFile(history2, "2345678.bh");
@@ -336,14 +338,14 @@
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
 
-        mHistory.startNextFile();       // 1000.bh
+        mHistory.startNextFile(mClock.realtime);       // 1000.bh
 
         mClock.realtime = 2000;
         mClock.uptime = 2000;
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
 
-        mHistory.startNextFile();       // 2000.bh
+        mHistory.startNextFile(mClock.realtime);       // 2000.bh
 
         mClock.realtime = 3000;
         mClock.uptime = 3000;
@@ -351,7 +353,7 @@
                 HistoryItem.EVENT_ALARM, "alarm", 42);
 
         // Flush accumulated history to disk
-        mHistory.startNextFile();
+        mHistory.startNextFile(mClock.realtime);
     }
 
     private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -518,7 +520,7 @@
         // Keep the preserved part of history short - we only need to capture the very tail of
         // history.
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000,
-                mStepDetailsCalculator, mClock, mTracer);
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
 
         mHistory.forceRecordAllHistory();
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 5df0acb..b1da1fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -40,6 +40,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 
 import org.junit.Rule;
@@ -64,6 +65,8 @@
             new BatteryUsageStatsRule(12345, mHistoryDir)
                     .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
                     .setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+    private MockClock mMockClock = mStatsRule.getMockClock();
+
     @Test
     public void test_getBatteryUsageStats() {
         BatteryStatsImpl batteryStats = prepareBatteryStats();
@@ -369,17 +372,23 @@
     public void testAggregateBatteryStats() {
         Context context = InstrumentationRegistry.getContext();
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
-        mStatsRule.setCurrentTime(5 * MINUTE_IN_MS);
+        MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+
+        setTime(5 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
-        BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context,
-                batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
-                new TestHandler(), Integer.MAX_VALUE);
-        batteryUsageStatsStore.onSystemReady();
+
+        PowerStatsStore powerStatsStore = new PowerStatsStore(
+                new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
+                new TestHandler(), null);
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                batteryStats, batteryUsageStatsStore);
+                batteryStats, powerStatsStore);
+
+        batteryStats.setBatteryResetListener(reason ->
+                powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(),
+                        provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));
 
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -389,7 +398,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(25 * MINUTE_IN_MS);
+        setTime(25 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
@@ -402,7 +411,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(55 * MINUTE_IN_MS);
+        setTime(55 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
@@ -416,7 +425,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(75 * MINUTE_IN_MS);
+        setTime(75 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
@@ -430,7 +439,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(95 * MINUTE_IN_MS);
+        setTime(95 * MINUTE_IN_MS);
 
         // Include the first and the second snapshot, but not the third or current
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
@@ -457,29 +466,41 @@
                 .of(180.0);
     }
 
+    private void setTime(long timeMs) {
+        mMockClock.currentTime = timeMs;
+        mMockClock.realtime = timeMs;
+    }
+
     @Test
     public void testAggregateBatteryStats_incompatibleSnapshot() {
         Context context = InstrumentationRegistry.getContext();
         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
         batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
 
-        BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+        PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
 
-        when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
-                .thenReturn(new long[]{1000, 2000});
-
-        when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+        PowerStatsSpan span0 = new PowerStatsSpan(0);
+        span0.addTimeFrame(0, 1000, 1234);
+        span0.addSection(new BatteryUsageStatsSection(
                 new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
-                        .setStatsDuration(1234).build());
+                        .setStatsDuration(1234).build()));
 
-        // Add a snapshot, with a different set of custom power components.  It should
-        // be skipped by the aggregation.
-        when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+        PowerStatsSpan span1 = new PowerStatsSpan(1);
+        span1.addTimeFrame(0, 2000, 4321);
+        span1.addSection(new BatteryUsageStatsSection(
                 new BatteryUsageStats.Builder(new String[]{"different"})
-                        .setStatsDuration(4321).build());
+                        .setStatsDuration(4321).build()));
+
+        when(powerStatsStore.getTableOfContents()).thenReturn(
+                List.of(span0.getMetadata(), span1.getMetadata()));
+
+        when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE))
+                .thenReturn(span0);
+        when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
+                .thenReturn(span1);
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                batteryStats, batteryUsageStatsStore);
+                batteryStats, powerStatsStore);
 
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(0, 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 93cbea6..0b10954 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
@@ -88,6 +88,10 @@
         mBatteryStats.onSystemReady();
     }
 
+    public MockClock getMockClock() {
+        return mMockClock;
+    }
+
     public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
         mPowerProfile.forceInitForTesting(mContext, xmlId);
         return this;
@@ -138,6 +142,22 @@
         return this;
     }
 
+    /**
+     * Mocks the CPU bracket count
+     */
+    public BatteryUsageStatsRule setCpuPowerBracketCount(int count) {
+        when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(count);
+        return this;
+    }
+
+    /**
+     * Mocks the CPU bracket for the given CPU scaling policy and step
+     */
+    public BatteryUsageStatsRule setCpuPowerBracket(int policy, int step, int bracket) {
+        when(mPowerProfile.getCpuPowerBracketForScalingStep(policy, step)).thenReturn(bracket);
+        return this;
+    }
+
     public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal,
             double value) {
         when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
deleted file mode 100644
index b846e3a..0000000
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.os.BatteryManager;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Xml;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.PowerProfile;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SuppressWarnings("GuardedBy")
-public class BatteryUsageStatsStoreTest {
-    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
-
-    private final MockClock mMockClock = new MockClock();
-    private MockBatteryStatsImpl mBatteryStats;
-    private BatteryUsageStatsStore mBatteryUsageStatsStore;
-    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-    private File mStoreDirectory;
-
-    @Before
-    public void setup() {
-        mMockClock.currentTime = 123;
-        mBatteryStats = new MockBatteryStatsImpl(mMockClock);
-        mBatteryStats.setNoAutoReset(true);
-        mBatteryStats.setPowerProfile(mock(PowerProfile.class));
-        mBatteryStats.onSystemReady();
-
-        Context context = InstrumentationRegistry.getContext();
-
-        mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest");
-        clearDirectory(mStoreDirectory);
-
-        mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats,
-                mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
-        mBatteryUsageStatsStore.onSystemReady();
-
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
-    }
-
-    @Test
-    public void testStoreSnapshot() {
-        mMockClock.currentTime = 1_600_000;
-        mMockClock.realtime = 1000;
-        mMockClock.uptime = 1000;
-
-        prepareBatteryStats();
-
-        mMockClock.realtime = 1_000_000;
-        mMockClock.uptime = 1_000_000;
-        mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
-        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
-        assertThat(timestamps).hasLength(1);
-        assertThat(timestamps[0]).isEqualTo(1_600_000);
-
-        final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats(
-                1_600_000);
-        assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123);
-        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
-        assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
-        assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
-        assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
-        assertThat(batteryUsageStats.getAggregateBatteryConsumer(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
-                .isEqualTo(600);  // (3_600_000 - 3_000_000) / 1000
-    }
-
-    @Test
-    public void testGarbageCollectOldSnapshots() throws Exception {
-        prepareBatteryStats();
-
-        mMockClock.realtime = 10_000_000;
-        mMockClock.uptime = 10_000_000;
-        mMockClock.currentTime = 10_000_000;
-
-        final int snapshotFileSize = getSnapshotFileSize();
-        final int numberOfSnapshots =
-                (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize);
-        for (int i = 0; i < numberOfSnapshots + 2; i++) {
-            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
-            mMockClock.realtime += 10_000_000;
-            mMockClock.uptime += 10_000_000;
-            mMockClock.currentTime += 10_000_000;
-            prepareBatteryStats();
-        }
-
-        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
-        Arrays.sort(timestamps);
-        assertThat(timestamps).hasLength(numberOfSnapshots);
-        // Two snapshots (10_000_000 and 20_000_000) should have been discarded
-        assertThat(timestamps[0]).isEqualTo(30_000_000);
-        assertThat(getDirectorySize(mStoreDirectory))
-                .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
-    }
-
-    @Test
-    public void testRemoveAllSnapshots() throws Exception {
-        prepareBatteryStats();
-
-        for (int i = 0; i < 3; i++) {
-            mMockClock.realtime += 10_000_000;
-            mMockClock.uptime += 10_000_000;
-            mMockClock.currentTime += 10_000_000;
-            prepareBatteryStats();
-
-            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-        }
-
-        assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
-
-        mBatteryUsageStatsStore.removeAllSnapshots();
-
-        assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
-    }
-
-    @Test
-    public void testSavingStatsdAtomPullTimestamp() {
-        mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
-        assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
-                .isEqualTo(1234);
-        mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
-        assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
-                .isEqualTo(5478);
-    }
-
-    private void prepareBatteryStats() {
-        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
-                mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime);
-        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
-                mMockClock.realtime + 500_000, mMockClock.uptime + 500_000,
-                mMockClock.currentTime + 500_000);
-    }
-
-    private void clearDirectory(File dir) {
-        if (dir.exists()) {
-            for (File child : dir.listFiles()) {
-                if (child.isDirectory()) {
-                    clearDirectory(child);
-                }
-                child.delete();
-            }
-        }
-    }
-
-    private long getDirectorySize(File dir) {
-        long size = 0;
-        if (dir.exists()) {
-            for (File child : dir.listFiles()) {
-                if (child.isDirectory()) {
-                    size += getDirectorySize(child);
-                } else {
-                    size += child.length();
-                }
-            }
-        }
-        return size;
-    }
-
-    private int getSnapshotFileSize() throws IOException {
-        BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats(
-                new BatteryUsageStatsQuery.Builder()
-                        .setMaxStatsAgeMs(0)
-                        .includePowerModels()
-                        .build());
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        TypedXmlSerializer serializer = Xml.newBinarySerializer();
-        serializer.setOutput(out, StandardCharsets.UTF_8.name());
-        serializer.startDocument(null, true);
-        stats.writeXml(serializer);
-        serializer.endDocument();
-        return out.toByteArray().length;
-    }
-
-    private static class TestHandler extends Handler {
-        TestHandler() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            msg.getCallback().run();
-            return true;
-        }
-    }
-}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
new file mode 100644
index 0000000..79084cc
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.LongArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CpuAggregatedPowerStatsProcessorTest {
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+            .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
+            .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300})
+            .setAveragePowerForCpuScalingPolicy(0, 360)
+            .setAveragePowerForCpuScalingPolicy(2, 480)
+            .setAveragePowerForCpuScalingStep(0, 0, 300)
+            .setAveragePowerForCpuScalingStep(0, 1, 400)
+            .setAveragePowerForCpuScalingStep(2, 0, 500)
+            .setCpuPowerBracketCount(3)
+            .setCpuPowerBracket(0, 0, 0)
+            .setCpuPowerBracket(0, 1, 1)
+            .setCpuPowerBracket(2, 0, 2);
+
+    private AggregatedPowerStatsConfig.PowerComponent mConfig;
+    private CpuAggregatedPowerStatsProcessor mProcessor;
+    private MockPowerComponentAggregatedPowerStats mStats;
+
+    @Before
+    public void setup() {
+        mConfig = new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+                .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
+
+        mProcessor = new CpuAggregatedPowerStatsProcessor(
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies());
+    }
+
+    @Test
+    public void powerProfileModel() {
+        mStats = new MockPowerComponentAggregatedPowerStats(mConfig, false);
+        mStats.setDeviceStats(
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON),
+                concat(
+                        values(3500, 4500, 3000),   // scaling steps
+                        values(2000, 1000),         // clusters
+                        values(5000)),              // uptime
+                3.113732);
+        mStats.setDeviceStats(
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON),
+                concat(
+                        values(6000, 6500, 4000),
+                        values(5000, 3000),
+                        values(7000)),
+                4.607245);
+        mStats.setDeviceStats(
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER),
+                concat(
+                        values(9000, 10000, 7000),
+                        values(8000, 6000),
+                        values(20000)),
+                7.331799);
+        mStats.setUidStats(24,
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+                values(400, 1500, 2000),  1.206947);
+        mStats.setUidStats(42,
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+                values(900, 1000, 1500), 1.016182);
+        mStats.setUidStats(42,
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND),
+                values(600, 500, 300), 0.385042);
+        mStats.setUidStats(42,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
+                values(1500, 2000, 1000), 1.252578);
+
+        mProcessor.finish(mStats);
+
+        mStats.verifyPowerEstimates();
+    }
+
+    @Test
+    public void energyConsumerModel() {
+        mStats = new MockPowerComponentAggregatedPowerStats(mConfig, true);
+        mStats.setDeviceStats(
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON),
+                concat(
+                        values(3500, 4500, 3000),           // scaling steps
+                        values(2000, 1000),                 // clusters
+                        values(5000),                       // uptime
+                        values(5_000_000L, 6_000_000L)),    // energy, uC
+                3.055555);
+        mStats.setDeviceStats(
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON),
+                concat(
+                        values(6000, 6500, 4000),
+                        values(5000, 3000),
+                        values(7000),
+                        values(5_000_000L, 6_000_000L)),    // same as above
+                3.055555);                                  // same as above - WAI
+        mStats.setDeviceStats(
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER),
+                concat(
+                        values(9000, 10000, 7000),
+                        values(8000, 6000),
+                        values(20000),
+                        values(8_000_000L, 18_000_000L)),
+                7.222222);
+        mStats.setUidStats(24,
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+                values(400, 1500, 2000),  1.449078);
+        mStats.setUidStats(42,
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+                values(900, 1000, 1500), 1.161902);
+        mStats.setUidStats(42,
+                states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND),
+                values(600, 500, 300), 0.355406);
+        mStats.setUidStats(42,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
+                values(1500, 2000, 1000), 0.80773);
+
+        mProcessor.finish(mStats);
+
+        mStats.verifyPowerEstimates();
+    }
+
+    private int[] states(int... states) {
+        return states;
+    }
+
+    private long[] values(long... values) {
+        return values;
+    }
+
+    private long[] concat(long[]... arrays) {
+        LongArray all = new LongArray();
+        for (long[] array : arrays) {
+            for (long value : array) {
+                all.add(value);
+            }
+        }
+        return all.toArray();
+    }
+
+    private static class MockPowerComponentAggregatedPowerStats extends
+            PowerComponentAggregatedPowerStats {
+        private final CpuPowerStatsCollector.StatsArrayLayout mStatsLayout;
+        private final PowerStats.Descriptor mDescriptor;
+        private HashMap<String, long[]> mDeviceStats = new HashMap<>();
+        private HashMap<String, long[]> mUidStats = new HashMap<>();
+        private HashSet<Integer> mUids = new HashSet<>();
+        private HashMap<String, Double> mExpectedDevicePower = new HashMap<>();
+        private HashMap<String, Double> mExpectedUidPower = new HashMap<>();
+
+        MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config,
+                boolean useEnergyConsumers) {
+            super(config);
+            mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout();
+            mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3);
+            mStatsLayout.addDeviceSectionCpuTimeByCluster(2);
+            mStatsLayout.addDeviceSectionUptime();
+            if (useEnergyConsumers) {
+                mStatsLayout.addDeviceSectionEnergyConsumers(2);
+            }
+            mStatsLayout.addDeviceSectionPowerEstimate();
+            mStatsLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0, 1, 2});
+            mStatsLayout.addUidSectionPowerEstimate();
+
+            PersistableBundle extras = new PersistableBundle();
+            mStatsLayout.toExtras(extras);
+            mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
+                    mStatsLayout.getDeviceStatsArrayLength(), mStatsLayout.getUidStatsArrayLength(),
+                    extras);
+        }
+
+        @Override
+        public PowerStats.Descriptor getPowerStatsDescriptor() {
+            return mDescriptor;
+        }
+
+        @Override
+        boolean getDeviceStats(long[] outValues, int[] deviceStates) {
+            long[] values = getDeviceStats(deviceStates);
+            System.arraycopy(values, 0, outValues, 0, values.length);
+            return true;
+        }
+
+        private long[] getDeviceStats(int[] deviceStates) {
+            String key = statesToString(getConfig().getDeviceStateConfig(), deviceStates);
+            long[] values = mDeviceStats.get(key);
+            return values == null ? new long[mDescriptor.statsArrayLength] : values;
+        }
+
+        void setDeviceStats(int[] states, long[] values, double expectedPowerEstimate) {
+            setDeviceStats(states, values);
+            mExpectedDevicePower.put(statesToString(getConfig().getDeviceStateConfig(), states),
+                    expectedPowerEstimate);
+        }
+
+        @Override
+        void setDeviceStats(int[] states, long[] values) {
+            String key = statesToString(getConfig().getDeviceStateConfig(), states);
+            mDeviceStats.put(key, Arrays.copyOf(values, mDescriptor.statsArrayLength));
+        }
+
+        @Override
+        boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
+            long[] values = getUidStats(uid, uidStates);
+            assertThat(values).isNotNull();
+            System.arraycopy(values, 0, outValues, 0, values.length);
+            return true;
+        }
+
+        private long[] getUidStats(int uid, int[] uidStates) {
+            String key = uid + " " + statesToString(getConfig().getUidStateConfig(), uidStates);
+            long[] values = mUidStats.get(key);
+            return values == null ? new long[mDescriptor.uidStatsArrayLength] : values;
+        }
+
+        void setUidStats(int uid, int[] states, long[] values, double expectedPowerEstimate) {
+            setUidStats(uid, states, values);
+            mExpectedUidPower.put(
+                    uid + " " + statesToString(getConfig().getUidStateConfig(), states),
+                    expectedPowerEstimate);
+        }
+
+        @Override
+        void setUidStats(int uid, int[] states, long[] values) {
+            mUids.add(uid);
+            String key = uid + " " + statesToString(getConfig().getUidStateConfig(), states);
+            mUidStats.put(key, Arrays.copyOf(values, mDescriptor.uidStatsArrayLength));
+        }
+
+        @Override
+        void collectUids(Collection<Integer> uids) {
+            uids.addAll(mUids);
+        }
+
+        void verifyPowerEstimates() {
+            StringBuilder mismatches = new StringBuilder();
+            for (Map.Entry<String, Double> entry : mExpectedDevicePower.entrySet()) {
+                String key = entry.getKey();
+                double expected = mExpectedDevicePower.get(key);
+                double actual = mStatsLayout.getDevicePowerEstimate(mDeviceStats.get(key));
+                if (Math.abs(expected - actual) > 0.005) {
+                    mismatches.append(key + " expected: " + expected + " actual: " + actual + "\n");
+                }
+            }
+            for (Map.Entry<String, Double> entry : mExpectedUidPower.entrySet()) {
+                String key = entry.getKey();
+                double expected = mExpectedUidPower.get(key);
+                double actual = mStatsLayout.getUidPowerEstimate(mUidStats.get(key));
+                if (Math.abs(expected - actual) > 0.005) {
+                    mismatches.append(key + " expected: " + expected + " actual: " + actual + "\n");
+                }
+            }
+            if (!mismatches.isEmpty()) {
+                fail("Unexpected power estimations:\n" + mismatches);
+            }
+        }
+
+        private String statesToString(MultiStateStats.States[] config, int[] states) {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < states.length; i++) {
+                sb.append(config[i].getName()).append("=").append(states[i]).append(" ");
+            }
+            return sb.toString();
+        }
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index f2ee6db..bc211df 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -20,16 +20,26 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.power.PowerStatsInternal;
 import android.util.SparseArray;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.frameworks.powerstatstests.R;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
@@ -40,85 +50,266 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CpuPowerStatsCollectorTest {
+    private Context mContext;
     private final MockClock mMockClock = new MockClock();
     private final HandlerThread mHandlerThread = new HandlerThread("test");
     private Handler mHandler;
-    private CpuPowerStatsCollector mCollector;
     private PowerStats mCollectedStats;
-    @Mock
     private PowerProfile mPowerProfile;
     @Mock
     private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
+    @Mock
+    private PowerStatsInternal mPowerStatsInternal;
+    private CpuScalingPolicies mCpuScalingPolicies;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getContext();
 
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
-        when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(2);
-        when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 0)).thenReturn(0);
-        when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 1)).thenReturn(1);
-        mCollector = new CpuPowerStatsCollector(new CpuScalingPolicies(
-                new SparseArray<>() {{
-                    put(0, new int[]{0});
-                }},
-                new SparseArray<>() {{
-                    put(0, new int[]{1, 12});
-                }}),
-                mPowerProfile, mHandler, mMockKernelCpuStatsReader, 60_000, mMockClock);
-        mCollector.addConsumer(stats -> mCollectedStats = stats);
-        mCollector.setEnabled(true);
+        when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true);
     }
 
     @Test
-    public void collectStats() {
-        mockKernelCpuStats(new SparseArray<>() {{
-                put(42, new long[]{100, 200});
-                put(99, new long[]{300, 600});
-            }}, 0, 1234);
+    public void powerBrackets_specifiedInPowerProfile() {
+        mPowerProfile = new PowerProfile(mContext);
+        mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets);
+        mCpuScalingPolicies = new CpuScalingPolicies(
+                new SparseArray<>() {{
+                    put(0, new int[]{0});
+                    put(4, new int[]{4});
+                }},
+                new SparseArray<>() {{
+                    put(0, new int[]{100});
+                    put(4, new int[]{400, 500});
+                }});
+
+        CpuPowerStatsCollector collector = createCollector(8, 0);
+
+        assertThat(getScalingStepToPowerBracketMap(collector))
+                .isEqualTo(new int[]{1, 1, 0});
+    }
+
+    @Test
+    public void powerBrackets_default_noEnergyConsumers() {
+        mPowerProfile = new PowerProfile(mContext);
+        mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+        mockCpuScalingPolicies(2);
+
+        CpuPowerStatsCollector collector = createCollector(3, 0);
+
+        assertThat(new String[]{
+                collector.getCpuPowerBracketDescription(0),
+                collector.getCpuPowerBracketDescription(1),
+                collector.getCpuPowerBracketDescription(2)})
+                .isEqualTo(new String[]{
+                        "0/300(10.0)",
+                        "0/1000(20.0), 0/2000(30.0), 4/300(25.0)",
+                        "4/1000(35.0), 4/2500(50.0), 4/3000(60.0)"});
+        assertThat(getScalingStepToPowerBracketMap(collector))
+                .isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2});
+    }
+
+    @Test
+    public void powerBrackets_moreBracketsThanStates() {
+        mPowerProfile = new PowerProfile(mContext);
+        mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+        mockCpuScalingPolicies(2);
+
+        CpuPowerStatsCollector collector = createCollector(8, 0);
+
+        assertThat(getScalingStepToPowerBracketMap(collector))
+                .isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6});
+    }
+
+    @Test
+    public void powerBrackets_energyConsumers() throws Exception {
+        mPowerProfile = new PowerProfile(mContext);
+        mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+        mockCpuScalingPolicies(2);
+        mockEnergyConsumers();
+
+        CpuPowerStatsCollector collector = createCollector(8, 2);
+
+        assertThat(getScalingStepToPowerBracketMap(collector))
+                .isEqualTo(new int[]{0, 1, 1, 2, 2, 3, 3});
+    }
+
+    @Test
+    public void powerStatsDescriptor() throws Exception {
+        mPowerProfile = new PowerProfile(mContext);
+        mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+        mockCpuScalingPolicies(2);
+        mockEnergyConsumers();
+
+        CpuPowerStatsCollector collector = createCollector(8, 2);
+        PowerStats.Descriptor descriptor = collector.getPowerStatsDescriptor();
+        assertThat(descriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
+        assertThat(descriptor.name).isEqualTo("cpu");
+        assertThat(descriptor.statsArrayLength).isEqualTo(13);
+        assertThat(descriptor.uidStatsArrayLength).isEqualTo(5);
+        CpuPowerStatsCollector.StatsArrayLayout layout =
+                new CpuPowerStatsCollector.StatsArrayLayout();
+        layout.fromExtras(descriptor.extras);
+
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+        layout.setTimeByScalingStep(deviceStats, 2, 42);
+        layout.setConsumedEnergy(deviceStats, 1, 43);
+        layout.setUptime(deviceStats, 44);
+        layout.setDevicePowerEstimate(deviceStats, 45);
+
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+        layout.setUidTimeByPowerBracket(uidStats, 3, 46);
+        layout.setUidPowerEstimate(uidStats, 47);
+
+        assertThat(layout.getCpuScalingStepCount()).isEqualTo(7);
+        assertThat(layout.getTimeByScalingStep(deviceStats, 2)).isEqualTo(42);
+
+        assertThat(layout.getCpuClusterEnergyConsumerCount()).isEqualTo(2);
+        assertThat(layout.getConsumedEnergy(deviceStats, 1)).isEqualTo(43);
+
+        assertThat(layout.getUptime(deviceStats)).isEqualTo(44);
+
+        assertThat(layout.getDevicePowerEstimate(deviceStats)).isEqualTo(45);
+
+        assertThat(layout.getScalingStepToPowerBracketMap()).isEqualTo(
+                new int[]{0, 1, 1, 2, 2, 3, 3});
+        assertThat(layout.getCpuPowerBracketCount()).isEqualTo(4);
+
+        assertThat(layout.getUidTimeByPowerBracket(uidStats, 3)).isEqualTo(46);
+        assertThat(layout.getUidPowerEstimate(uidStats)).isEqualTo(47);
+    }
+
+    @Test
+    public void collectStats() throws Exception {
+        mockCpuScalingPolicies(1);
+        mockPowerProfile();
+        mockEnergyConsumers();
+
+        CpuPowerStatsCollector collector = createCollector(8, 0);
+        CpuPowerStatsCollector.StatsArrayLayout layout =
+                new CpuPowerStatsCollector.StatsArrayLayout();
+        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+
+        mockKernelCpuStats(new long[]{1111, 2222, 3333},
+                new SparseArray<>() {{
+                    put(42, new long[]{100, 200});
+                    put(99, new long[]{300, 600});
+                }}, 0, 1234);
 
         mMockClock.uptime = 1000;
-        mCollector.forceSchedule();
+        collector.forceSchedule();
         waitForIdle();
 
         assertThat(mCollectedStats.durationMs).isEqualTo(1234);
-        assertThat(mCollectedStats.uidStats.get(42)).isEqualTo(new long[]{100, 200});
-        assertThat(mCollectedStats.uidStats.get(99)).isEqualTo(new long[]{300, 600});
 
-        mockKernelCpuStats(new SparseArray<>() {{
-                put(42, new long[]{123, 234});
-                put(99, new long[]{345, 678});
-            }}, 1234, 3421);
+        assertThat(layout.getCpuScalingStepCount()).isEqualTo(3);
+        assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 0)).isEqualTo(1111);
+        assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 1)).isEqualTo(2222);
+        assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 2)).isEqualTo(3333);
+
+        assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(0);
+        assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(0);
+
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0))
+                .isEqualTo(100);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1))
+                .isEqualTo(200);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0))
+                .isEqualTo(300);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1))
+                .isEqualTo(600);
+
+        mockKernelCpuStats(new long[]{5555, 4444, 3333},
+                new SparseArray<>() {{
+                    put(42, new long[]{123, 234});
+                    put(99, new long[]{345, 678});
+                }}, 1234, 3421);
 
         mMockClock.uptime = 2000;
-        mCollector.forceSchedule();
+        collector.forceSchedule();
         waitForIdle();
 
         assertThat(mCollectedStats.durationMs).isEqualTo(3421 - 1234);
-        assertThat(mCollectedStats.uidStats.get(42)).isEqualTo(new long[]{23, 34});
-        assertThat(mCollectedStats.uidStats.get(99)).isEqualTo(new long[]{45, 78});
+
+        assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 0)).isEqualTo(4444);
+        assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 1)).isEqualTo(2222);
+        assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 2)).isEqualTo(0);
+
+        // 500 * 1000 / 3500
+        assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(143);
+        // 700 * 1000 / 3500
+        assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(200);
+
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0))
+                .isEqualTo(23);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1))
+                .isEqualTo(34);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0))
+                .isEqualTo(45);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1))
+                .isEqualTo(78);
     }
 
-    private void mockKernelCpuStats(SparseArray<long[]> uidToCpuStats,
+    private void mockCpuScalingPolicies(int clusterCount) {
+        SparseArray<int[]> cpus = new SparseArray<>();
+        SparseArray<int[]> freqs = new SparseArray<>();
+        cpus.put(0, new int[]{0, 1, 2, 3});
+        freqs.put(0, new int[]{300000, 1000000, 2000000});
+        if (clusterCount == 2) {
+            cpus.put(4, new int[]{4, 5});
+            freqs.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+        }
+        mCpuScalingPolicies = new CpuScalingPolicies(cpus, freqs);
+    }
+
+    private void mockPowerProfile() {
+        mPowerProfile = mock(PowerProfile.class);
+        when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(2);
+        when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 0)).thenReturn(0);
+        when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 1)).thenReturn(1);
+        when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 2)).thenReturn(1);
+    }
+
+    private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
+            int defaultCpuPowerBracketsPerEnergyConsumer) {
+        CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies,
+                mPowerProfile, mHandler, mMockKernelCpuStatsReader, () -> mPowerStatsInternal,
+                () -> 3500, 60_000, mMockClock, defaultCpuPowerBrackets,
+                defaultCpuPowerBracketsPerEnergyConsumer);
+        collector.addConsumer(stats -> mCollectedStats = stats);
+        collector.setEnabled(true);
+        return collector;
+    }
+
+    private void mockKernelCpuStats(long[] deviceStats, SparseArray<long[]> uidToCpuStats,
             long expectedLastUpdateTimestampMs, long newLastUpdateTimestampMs) {
         when(mMockKernelCpuStatsReader.nativeReadCpuStats(
                 any(CpuPowerStatsCollector.KernelCpuStatsCallback.class),
-                any(int[].class), anyLong(), any(long[].class)))
+                any(int[].class), anyLong(), any(long[].class), any(long[].class)))
                 .thenAnswer(invocation -> {
                     CpuPowerStatsCollector.KernelCpuStatsCallback callback =
                             invocation.getArgument(0);
                     int[] powerBucketIndexes = invocation.getArgument(1);
                     long lastTimestamp = invocation.getArgument(2);
-                    long[] tempStats = invocation.getArgument(3);
+                    long[] cpuTimeByScalingStep = invocation.getArgument(3);
+                    long[] tempStats = invocation.getArgument(4);
 
-                    assertThat(powerBucketIndexes).isEqualTo(new int[]{0, 1});
+                    assertThat(powerBucketIndexes).isEqualTo(new int[]{0, 1, 1});
                     assertThat(lastTimestamp / 1000000L).isEqualTo(expectedLastUpdateTimestampMs);
                     assertThat(tempStats).hasLength(2);
 
+                    System.arraycopy(deviceStats, 0, cpuTimeByScalingStep, 0,
+                            cpuTimeByScalingStep.length);
+
                     for (int i = 0; i < uidToCpuStats.size(); i++) {
                         int uid = uidToCpuStats.keyAt(i);
                         long[] cpuStats = uidToCpuStats.valueAt(i);
@@ -129,6 +320,67 @@
                 });
     }
 
+    @SuppressWarnings("unchecked")
+    private void mockEnergyConsumers() throws Exception {
+        when(mPowerStatsInternal.getEnergyConsumerInfo())
+                .thenReturn(new EnergyConsumer[]{
+                        new EnergyConsumer() {{
+                            id = 1;
+                            type = EnergyConsumerType.CPU_CLUSTER;
+                            ordinal = 0;
+                            name = "CPU0";
+                        }},
+                        new EnergyConsumer() {{
+                            id = 2;
+                            type = EnergyConsumerType.CPU_CLUSTER;
+                            ordinal = 1;
+                            name = "CPU4";
+                        }},
+                        new EnergyConsumer() {{
+                            id = 3;
+                            type = EnergyConsumerType.BLUETOOTH;
+                            name = "BT";
+                        }},
+                });
+
+        CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class);
+        when(future1.get(anyLong(), any(TimeUnit.class)))
+                .thenReturn(new EnergyConsumerResult[]{
+                        new EnergyConsumerResult() {{
+                            id = 1;
+                            energyUWs = 1000;
+                        }},
+                        new EnergyConsumerResult() {{
+                            id = 2;
+                            energyUWs = 2000;
+                        }}
+                });
+
+        CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class);
+        when(future2.get(anyLong(), any(TimeUnit.class)))
+                .thenReturn(new EnergyConsumerResult[]{
+                        new EnergyConsumerResult() {{
+                            id = 1;
+                            energyUWs = 1500;
+                        }},
+                        new EnergyConsumerResult() {{
+                            id = 2;
+                            energyUWs = 2700;
+                        }}
+                });
+
+        when(mPowerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2})))
+                .thenReturn(future1)
+                .thenReturn(future2);
+    }
+
+    private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) {
+        CpuPowerStatsCollector.StatsArrayLayout layout =
+                new CpuPowerStatsCollector.StatsArrayLayout();
+        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+        return layout.getScalingStepToPowerBracketMap();
+    }
+
     private void waitForIdle() {
         ConditionVariable done = new ConditionVariable();
         mHandler.post(done::open);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 4ecee9f..eb03a6c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -122,7 +122,7 @@
         // 4 * 10 = 40 bits needed to represent the composite state
         MultiStateStats.States[] states = new MultiStateStats.States[10];
         for (int i = 0; i < states.length; i++) {
-            states[i] = new MultiStateStats.States(true, labels);
+            states[i] = new MultiStateStats.States("foo", true, labels);
         }
         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
                 () -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
@@ -180,7 +180,7 @@
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw, true);
-        multiStateStats.dump(pw);
+        multiStateStats.dump(pw, Arrays::toString);
         assertThat(sw.toString()).isEqualTo(
                 "plugged-in fg [25, 50]\n"
                 + "on-battery fg [25, 50]\n"
@@ -191,8 +191,8 @@
     private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
             boolean trackProcState, boolean trackScreenState) {
         return new MultiStateStats.Factory(DIMENSION_COUNT,
-                new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
-                new MultiStateStats.States(trackProcState,
+                new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"),
+                new MultiStateStats.States("ps", trackProcState,
                         BatteryConsumer.processStateToString(
                                 BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
                         BatteryConsumer.processStateToString(
@@ -203,7 +203,7 @@
                                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
                         BatteryConsumer.processStateToString(
                                 BatteryConsumer.PROCESS_STATE_CACHED)),
-                new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+                new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));
     }
 
     private FactorySubject assertThatCpuPerformanceStatsFactory(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 47de443..b52fc8a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,11 +24,14 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
+import android.text.format.DateFormat;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
 
 import org.junit.Before;
@@ -36,16 +39,19 @@
 import org.junit.runner.RunWith;
 
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PowerStatsAggregatorTest {
     private static final int TEST_POWER_COMPONENT = 77;
     private static final int TEST_UID = 42;
+    private static final long START_TIME = 1234;
 
     private final MockClock mClock = new MockClock();
-    private long mStartTime;
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);
     private BatteryStatsHistory mHistory;
     private PowerStatsAggregator mAggregator;
     private int mAggregatedStatsCount;
@@ -53,25 +59,25 @@
     @Before
     public void setup() throws ParseException {
         mHistory = new BatteryStatsHistory(32, 1024,
-                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
-        mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
-                .parse("2008-09-23 08:00").getTime();
-        mClock.currentTime = mStartTime;
+                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
+                mMonotonicClock);
 
-        PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
-        builder.trackPowerComponent(TEST_POWER_COMPONENT)
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(TEST_POWER_COMPONENT)
                 .trackDeviceStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN)
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
                 .trackUidStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN,
-                        PowerStatsAggregator.STATE_PROCESS_STATE);
-        mAggregator = builder.build();
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+        mAggregator = new PowerStatsAggregator(config, mHistory);
     }
 
     @Test
     public void stateUpdates() {
+        mClock.currentTime = 1222156800000L;    // An important date in world history
+
         mHistory.forceRecordAllHistory();
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
         mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
@@ -98,15 +104,32 @@
         mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
                 BatteryConsumer.PROCESS_STATE_BACKGROUND);
 
-        advance(3000);
+        advance(1000);
+
+        mClock.currentTime += 60 * 60 * 1000;       // one hour
+        mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime);
+
+        advance(2000);
 
         powerStats.stats = new long[]{20000};
         powerStats.uidStats.put(TEST_UID, new long[]{4444});
         mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
 
-        mAggregator.aggregateBatteryStats(0, 0, stats -> {
+        mAggregator.aggregatePowerStats(0, 0, stats -> {
             assertThat(mAggregatedStatsCount++).isEqualTo(0);
-            assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+            assertThat(stats.getStartTime()).isEqualTo(START_TIME);
+
+            List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+            assertThat(clockUpdates).hasSize(2);
+
+            AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0);
+            assertThat(clockUpdate0.monotonicTime).isEqualTo(1234);
+            assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00");
+
+            AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1);
+            assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000);
+            assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03");
+
             assertThat(stats.getDuration()).isEqualTo(5000);
 
             long[] values = new long[1];
@@ -115,40 +138,47 @@
                     TEST_POWER_COMPONENT);
 
             assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                    PowerStatsAggregator.POWER_STATE_OTHER,
-                    PowerStatsAggregator.SCREEN_STATE_ON}))
+                    AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{10000});
 
             assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                    PowerStatsAggregator.POWER_STATE_BATTERY,
-                    PowerStatsAggregator.SCREEN_STATE_OTHER}))
+                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{20000});
 
             assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                    PowerStatsAggregator.POWER_STATE_OTHER,
-                    PowerStatsAggregator.SCREEN_STATE_ON,
+                    AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_ON,
                     BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{1234});
 
             assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                    PowerStatsAggregator.POWER_STATE_BATTERY,
-                    PowerStatsAggregator.SCREEN_STATE_OTHER,
+                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
                     BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{1111});
 
             assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                    PowerStatsAggregator.POWER_STATE_BATTERY,
-                    PowerStatsAggregator.SCREEN_STATE_OTHER,
+                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
                     BatteryConsumer.PROCESS_STATE_BACKGROUND}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{3333});
         });
     }
 
+    @NonNull
+    private static CharSequence formatDateTime(long timeInMillis) {
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+        cal.setTimeInMillis(timeInMillis);
+        return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+    }
+
     @Test
     public void incompatiblePowerStats() {
         mHistory.forceRecordAllHistory();
@@ -181,39 +211,39 @@
 
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
 
-        mAggregator.aggregateBatteryStats(0, 0, stats -> {
+        mAggregator.aggregatePowerStats(0, 0, stats -> {
             long[] values = new long[1];
 
             PowerComponentAggregatedPowerStats powerComponentStats =
                     stats.getPowerComponentStats(TEST_POWER_COMPONENT);
 
             if (mAggregatedStatsCount == 0) {
-                assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+                assertThat(stats.getStartTime()).isEqualTo(START_TIME);
                 assertThat(stats.getDuration()).isEqualTo(2000);
 
                 assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                        PowerStatsAggregator.POWER_STATE_OTHER,
-                        PowerStatsAggregator.SCREEN_STATE_ON}))
+                        AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{10000});
                 assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                        PowerStatsAggregator.POWER_STATE_OTHER,
-                        PowerStatsAggregator.SCREEN_STATE_ON,
+                        AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON,
                         BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{1234});
             } else if (mAggregatedStatsCount == 1) {
-                assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+                assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000);
                 assertThat(stats.getDuration()).isEqualTo(1000);
 
                 assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                        PowerStatsAggregator.POWER_STATE_BATTERY,
-                        PowerStatsAggregator.SCREEN_STATE_ON}))
+                        AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{20000});
                 assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                        PowerStatsAggregator.POWER_STATE_BATTERY,
-                        PowerStatsAggregator.SCREEN_STATE_ON,
+                        AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON,
                         BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{4444});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
new file mode 100644
index 0000000..0e58787
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsSchedulerTest {
+    private PowerStatsStore mPowerStatsStore;
+    private Handler mHandler;
+    private MockClock mClock = new MockClock();
+    private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+    private MockBatteryStatsImpl mBatteryStats;
+    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private PowerStatsScheduler mPowerStatsScheduler;
+    private PowerProfile mPowerProfile;
+    private PowerStatsAggregator mPowerStatsAggregator;
+    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+    @Before
+    public void setup() {
+        final Context context = InstrumentationRegistry.getContext();
+
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+        mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+        mClock.realtime = 7654321;
+
+        HandlerThread bgThread = new HandlerThread("bg thread");
+        bgThread.start();
+        File systemDir = context.getCacheDir();
+        mHandler = new Handler(bgThread.getLooper());
+        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
+        mPowerProfile = mock(PowerProfile.class);
+        when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
+        mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
+        mPowerStatsAggregator = mock(PowerStatsAggregator.class);
+        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+                TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
+                mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void storeAggregatePowerStats() {
+        mPowerStatsStore.reset();
+
+        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+        mPowerStatsStore.storeAggregatedPowerStats(
+                createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
+                        123));
+
+        long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
+        mClock.realtime += delayBeforeAggregating;
+        mClock.currentTime += delayBeforeAggregating;
+
+        doAnswer(invocation -> {
+            // The first span is longer than 30 min, because the end time is being aligned with
+            // the wall clock.  Subsequent spans should be precisely 30 minutes.
+            long startTime = invocation.getArgument(0);
+            long endTime = invocation.getArgument(1);
+            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+            long startTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+            long endTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+            assertThat(startTime).isEqualTo(7654321 + 123);
+            assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
+            assertThat(Instant.ofEpochMilli(endTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+
+            consumer.accept(
+                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+            return null;
+        }).doAnswer(invocation -> {
+            long startTime = invocation.getArgument(0);
+            long endTime = invocation.getArgument(1);
+            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+            long startTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+            long endTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+            assertThat(Instant.ofEpochMilli(startTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+            assertThat(Instant.ofEpochMilli(endTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
+
+            consumer.accept(
+                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+            return null;
+        }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
+                any(Consumer.class));
+
+        mPowerStatsScheduler.schedulePowerStatsAggregation();
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+
+        verify(mPowerStatsAggregator, times(2))
+                .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
+
+        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+        assertThat(contents).hasSize(3);
+        // Skip the first entry, which was placed in the store at the beginning of this test
+        PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
+        PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
+        assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+        assertThat(timeFrame2.startMonotonicTime)
+                .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
+        assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
+                .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+        assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
+    }
+
+    private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
+            long duration) {
+        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+        stats.addClockUpdate(monotonicTime, currentTime);
+        stats.setDuration(duration);
+        return stats;
+    }
+
+    @Test
+    public void storeBatteryUsageStatsOnReset() {
+        mBatteryStats.forceRecordAllHistory();
+        synchronized (mBatteryStats) {
+            mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true,
+                    BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
+        }
+
+        mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false);
+
+        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+        mPowerStatsScheduler.start(true);
+
+        synchronized (mBatteryStats) {
+            mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime);
+        }
+
+        mClock.realtime += 60000;
+        mClock.currentTime += 60000;
+
+        synchronized (mBatteryStats) {
+            mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime);
+        }
+
+        mClock.realtime += 60000;
+        mClock.currentTime += 60000;
+
+        // Battery stats reset should have the side-effect of saving accumulated battery usage stats
+        synchronized (mBatteryStats) {
+            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+        }
+
+        // Await completion
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+
+        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+        assertThat(contents).hasSize(1);
+
+        PowerStatsSpan.Metadata metadata = contents.get(0);
+
+        PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+                BatteryUsageStatsSection.TYPE);
+        assertThat(span).isNotNull();
+
+        List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
+        assertThat(timeFrames).hasSize(1);
+        assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
+        assertThat(timeFrames.get(0).duration).isEqualTo(120000);
+
+        List<PowerStatsSpan.Section> sections = span.getSections();
+        assertThat(sections).hasSize(1);
+
+        PowerStatsSpan.Section section = sections.get(0);
+        assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
+        BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+        assertThat(bus.getAggregateBatteryConsumer(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+                .isEqualTo(60000);
+    }
+
+    @Test
+    public void alignToWallClock() {
+        // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+                123 + TimeUnit.HOURS.toMillis(2),
+                Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT1M30S").toMillis());
+
+        // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+                123 + TimeUnit.HOURS.toMillis(2),
+                Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT2M45S").toMillis());
+
+        // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1),
+                123 + TimeUnit.HOURS.toMillis(2),
+                Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT15S").toMillis());
+
+        // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec -
+        // rounded to the next 3 hours
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3),
+                123 + TimeUnit.HOURS.toMillis(9),
+                Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT1H46M30S").toMillis());
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
new file mode 100644
index 0000000..d3628b5
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
+public class PowerStatsStoreTest {
+    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
+
+    private PowerStatsStore mPowerStatsStore;
+    private File mStoreDirectory;
+
+    @Before
+    public void setup() {
+        Context context = InstrumentationRegistry.getContext();
+
+        mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest");
+        clearDirectory(mStoreDirectory);
+
+        mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
+                MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES,
+                new TestHandler(),
+                (sectionType, parser) -> {
+                    if (sectionType.equals(TestSection.TYPE)) {
+                        return TestSection.readXml(parser);
+                    }
+                    return null;
+                });
+    }
+
+    @Test
+    public void garbageCollectOldSpans() throws Exception {
+        int spanSize = 500;
+        final int numberOfSnaps =
+                (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize);
+        for (int i = 0; i < numberOfSnaps + 2; i++) {
+            PowerStatsSpan span = new PowerStatsSpan(i);
+            span.addSection(new TestSection(i, spanSize));
+            mPowerStatsStore.storePowerStatsSpan(span);
+        }
+
+        assertThat(getDirectorySize(mStoreDirectory))
+                .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
+
+        List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+        assertThat(toc.size()).isLessThan(numberOfSnaps);
+        int minPreservedSpanId = numberOfSnaps - toc.size();
+        for (PowerStatsSpan.Metadata metadata : toc) {
+            assertThat(metadata.getId()).isAtLeast(minPreservedSpanId);
+        }
+    }
+
+    @Test
+    public void reset() throws Exception {
+        for (int i = 0; i < 3; i++) {
+            PowerStatsSpan span = new PowerStatsSpan(i);
+            span.addSection(new TestSection(i, 42));
+            mPowerStatsStore.storePowerStatsSpan(span);
+        }
+
+        assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
+
+        mPowerStatsStore.reset();
+
+        assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
+    }
+
+    private void clearDirectory(File dir) {
+        if (dir.exists()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    clearDirectory(child);
+                }
+                child.delete();
+            }
+        }
+    }
+
+    private long getDirectorySize(File dir) {
+        long size = 0;
+        if (dir.exists()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    size += getDirectorySize(child);
+                } else {
+                    size += child.length();
+                }
+            }
+        }
+        return size;
+    }
+
+    private static class TestSection extends PowerStatsSpan.Section {
+        public static final String TYPE = "much-text";
+
+        private final int mSize;
+        private final int mValue;
+
+        TestSection(int value, int size) {
+            super(TYPE);
+            mSize = size;
+            mValue = value;
+        }
+
+        @Override
+        void write(TypedXmlSerializer serializer) throws IOException {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < mSize; i++) {
+                sb.append("X");
+            }
+            serializer.startTag(null, "much-text");
+            serializer.attributeInt(null, "value", mValue);
+            serializer.text(sb.toString());
+            serializer.endTag(null, "much-text");
+        }
+
+        public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException {
+            TestSection section = null;
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals("much-text"))) {
+                if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) {
+                    section = new TestSection(parser.getAttributeInt(null, "value"), 0);
+                }
+            }
+            return section;
+        }
+    }
+
+    private static class TestHandler extends Handler {
+        TestHandler() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            msg.getCallback().run();
+            return true;
+        }
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 2ffe4aa..df46054 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -1079,7 +1079,7 @@
         GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();
         mService.getSupportedPowerMonitorsImpl(result);
         assertThat(result.powerMonitors).isNotNull();
-        assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList())
+        assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList())
                 .containsAtLeast(
                         "energyconsumer0",
                         "BLUETOOTH/1",
@@ -1130,7 +1130,7 @@
         mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
         Map<String, PowerMonitor> map =
                 Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
-                        .collect(Collectors.toMap(pm -> pm.name, pm -> pm));
+                        .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm));
         PowerMonitor consumer1 = map.get("energyconsumer0");
         PowerMonitor consumer2 = map.get("BLUETOOTH/1");
         PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
@@ -1196,6 +1196,6 @@
         supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
         mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
         assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
-                .map(pm -> pm.name).toList()).contains("energyconsumer0");
+                .map(PowerMonitor::getName).toList()).contains("energyconsumer0");
     }
 }
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 0115db6..ef19ba1 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -40,6 +40,7 @@
             mediaSharedWithParent='true'
             credentialShareableWithParent='false'
             showInSettings='23'
+            hideInSettingsInQuietMode='true'
             inheritDevicePolicy='450'
             deleteAppWithParent='false'
             alwaysVisible='true'
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 49f22ec..b9e45ba 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -33,6 +34,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -53,14 +55,21 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.UserHandle;
+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.ArraySet;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
@@ -93,8 +102,13 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
@@ -104,6 +118,10 @@
     public final A11yTestableContext mTestableContext = new A11yTestableContext(
             ApplicationProvider.getApplicationContext());
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int ACTION_ID = 20;
     private static final String LABEL = "label";
     private static final String INTENT_ACTION = "TESTACTION";
@@ -204,6 +222,8 @@
                 mA11yms.getCurrentUserIdLocked());
         when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
         mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+        mMockResolveInfo.serviceInfo.packageName = "packageName";
+        mMockResolveInfo.serviceInfo.name = "className";
         mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
 
         when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
@@ -581,6 +601,141 @@
                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
     }
 
+    @Test
+    public void testPackagesForceStopped_disablesRelevantService() {
+        final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+        info_a.setComponentName(COMPONENT_NAME);
+        final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+        info_b.setComponentName(new ComponentName("package", "class"));
+
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mInstalledServices.clear();
+        userState.mInstalledServices.add(info_a);
+        userState.mInstalledServices.add(info_b);
+        userState.mEnabledServices.clear();
+        userState.mEnabledServices.add(info_a.getComponentName());
+        userState.mEnabledServices.add(info_b.getComponentName());
+
+        synchronized (mA11yms.getLock()) {
+            mA11yms.onPackagesForceStoppedLocked(
+                    new String[]{info_a.getComponentName().getPackageName()}, userState);
+        }
+
+        //Assert user state change
+        userState = mA11yms.getCurrentUserState();
+        assertThat(userState.mEnabledServices).containsExactly(info_b.getComponentName());
+        //Assert setting change
+        final Set<ComponentName> componentsFromSetting = new ArraySet<>();
+        mA11yms.readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userState.mUserId, componentsFromSetting);
+        assertThat(componentsFromSetting).containsExactly(info_b.getComponentName());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP)
+    public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() {
+        final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+        info_a.setComponentName(COMPONENT_NAME);
+        info_a.flags = FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+        final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+        info_b.setComponentName(new ComponentName("package", "class"));
+
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mInstalledServices.clear();
+        userState.mInstalledServices.add(info_a);
+        userState.mInstalledServices.add(info_b);
+        userState.mAccessibilityButtonTargets.clear();
+        userState.mAccessibilityButtonTargets.add(info_a.getComponentName().flattenToString());
+        userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
+
+        // despite force stopping both packages, only the first service has the relevant flag,
+        // so only the first should be removed.
+        synchronized (mA11yms.getLock()) {
+            mA11yms.onPackagesForceStoppedLocked(
+                    new String[]{
+                            info_a.getComponentName().getPackageName(),
+                            info_b.getComponentName().getPackageName()},
+                    userState);
+        }
+
+        //Assert user state change
+        userState = mA11yms.getCurrentUserState();
+        assertThat(userState.mAccessibilityButtonTargets).containsExactly(
+                info_b.getComponentName().flattenToString());
+        //Assert setting change
+        final Set<String> targetsFromSetting = new ArraySet<>();
+        mA11yms.readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                userState.mUserId, str -> str, targetsFromSetting);
+        assertThat(targetsFromSetting).containsExactly(info_b.getComponentName().flattenToString());
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    // Test old behavior to validate lock detection for the old (locked access) case.
+    public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        packageIntent.setData(Uri.parse("test://package"));
+        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+        packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+        assertThat(lockState.get()).containsExactly(true);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        packageIntent.setData(Uri.parse("test://package"));
+        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+        packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+        assertThat(lockState.get()).containsExactly(false);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+
+        assertThat(lockState.get()).containsExactly(false);
+    }
+
+    // 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.
+    private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() {
+        final AtomicReference<Set<Boolean>> lockState =
+                new AtomicReference<>(new HashSet<Boolean>());
+        doAnswer((Answer<XmlResourceParser>) invocation -> {
+            lockState.updateAndGet(set -> {
+                set.add(mA11yms.unsafeIsLockHeld());
+                return set;
+            });
+            return null;
+        }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any());
+        return lockState;
+    }
+
     private void mockManageAccessibilityGranted(TestableContext context) {
         context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
                 PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 63281b7..52a5d8f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -155,7 +155,8 @@
         mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
         mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
-        mUserState.setDisplayMagnificationEnabledLocked(true);
+        mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+        mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
         mUserState.setAutoclickEnabledLocked(true);
         mUserState.setUserNonInteractiveUiTimeoutLocked(30);
         mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -177,7 +178,8 @@
         assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
         assertNull(mUserState.getTargetAssignedToAccessibilityButton());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
-        assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
+        assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
+        assertFalse(mUserState.isMagnificationTwoFingerTripleTapEnabledLocked());
         assertFalse(mUserState.isAutoclickEnabledLocked());
         assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
         assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
index 8a057df..0988eea 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
@@ -366,6 +366,7 @@
     private void assumeCameraTorchAvailable() {
         assumeTrue(mCameraManager != null);
         assumeTrue(!mCameraInfoMap.isEmpty());
+        assumeTrue(mCameraInfoMap.values().stream().anyMatch(info -> info.mIsValid));
     }
 
     private void simulateCallSequence() throws InterruptedException {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 3808f30..bfaf4959 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,6 +40,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.MagnificationConfig;
+import android.companion.virtual.IVirtualDeviceListener;
 import android.companion.virtual.IVirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
@@ -50,6 +53,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.flag.junit.SetFlagsRule;
 import android.util.ArraySet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -74,6 +78,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -94,6 +99,9 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
+    @Rule
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock private Context mMockContext;
     @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
     @Mock private AccessibilityWindowManager mMockA11yWindowManager;
@@ -114,6 +122,8 @@
 
     @Before
     public void setup() throws RemoteException {
+        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+
         MockitoAnnotations.initMocks(this);
         final Resources resources = InstrumentationRegistry.getContext().getResources();
 
@@ -121,6 +131,8 @@
                 resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width);
         mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color);
         when(mMockContext.getResources()).thenReturn(resources);
+        when(mMockContext.getMainExecutor())
+                .thenReturn(InstrumentationRegistry.getTargetContext().getMainExecutor());
 
         when(mMockVirtualDeviceManagerInternal.getDeviceIdsForUid(anyInt())).thenReturn(
                 new ArraySet(Set.of(DEVICE_ID)));
@@ -416,6 +428,101 @@
         assertThat(focusStrokeWidth).isEqualTo(mFocusStrokeWidthDefaultValue);
     }
 
+    @Test
+    public void testRegisterProxy_registersVirtualDeviceListener() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+
+        verify(mMockIVirtualDeviceManager, times(1)).registerVirtualDeviceListener(any());
+    }
+
+    @Test
+    public void testRegisterMultipleProxies_registersOneVirtualDeviceListener()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+        registerProxy(DISPLAY_2_ID);
+
+        verify(mMockIVirtualDeviceManager, times(1)).registerVirtualDeviceListener(any());
+    }
+
+    @Test
+    public void testUnregisterProxy_unregistersVirtualDeviceListener() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+
+        mProxyManager.unregisterProxy(DISPLAY_ID);
+
+        verify(mMockIVirtualDeviceManager, times(1)).unregisterVirtualDeviceListener(any());
+    }
+
+    @Test
+    public void testUnregisterProxy_onlyUnregistersVirtualDeviceListenerOnLastProxyRemoval()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+        registerProxy(DISPLAY_2_ID);
+
+        mProxyManager.unregisterProxy(DISPLAY_ID);
+        verify(mMockIVirtualDeviceManager, never()).unregisterVirtualDeviceListener(any());
+
+        mProxyManager.unregisterProxy(DISPLAY_2_ID);
+        verify(mMockIVirtualDeviceManager, times(1)).unregisterVirtualDeviceListener(any());
+    }
+
+    @Test
+    public void testRegisteredProxy_virtualDeviceClosed_proxyClosed()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+
+        assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue();
+        assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue();
+
+        ArgumentCaptor<IVirtualDeviceListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(IVirtualDeviceListener.class);
+        verify(mMockIVirtualDeviceManager, times(1))
+                .registerVirtualDeviceListener(listenerArgumentCaptor.capture());
+
+        listenerArgumentCaptor.getValue().onVirtualDeviceClosed(DEVICE_ID);
+
+        verify(mMockProxySystemSupport, timeout(5_000)).removeDeviceIdLocked(DEVICE_ID);
+
+        assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isFalse();
+        assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isFalse();
+    }
+
+    @Test
+    public void testRegisteredProxy_unrelatedVirtualDeviceClosed_proxyNotClosed()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+
+        assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue();
+        assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue();
+
+        ArgumentCaptor<IVirtualDeviceListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(IVirtualDeviceListener.class);
+        verify(mMockIVirtualDeviceManager, times(1))
+                .registerVirtualDeviceListener(listenerArgumentCaptor.capture());
+
+        listenerArgumentCaptor.getValue().onVirtualDeviceClosed(DEVICE_ID + 1);
+
+        assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue();
+        assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue();
+    }
+
+    @Test
+    public void testRegisterProxy_doesNotRegisterVirtualDeviceListener_flagDisabled()
+            throws RemoteException {
+        mSetFlagsRule.disableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
+        registerProxy(DISPLAY_ID);
+        mProxyManager.unregisterProxy(DISPLAY_ID);
+
+        verify(mMockIVirtualDeviceManager, never()).registerVirtualDeviceListener(any());
+        verify(mMockIVirtualDeviceManager, never()).unregisterVirtualDeviceListener(any());
+    }
+
     private void registerProxy(int displayId) {
         try {
             mProxyManager.registerProxy(mMockAccessibilityServiceClient, displayId, anyInt(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1cd61e9..efcdbd4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,6 +44,10 @@
 import android.graphics.PointF;
 import android.os.Looper;
 import android.os.SystemClock;
+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.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -56,6 +60,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.utils.GestureLogParser;
 import com.android.server.testutils.OffsettableClock;
 
@@ -76,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+
 @RunWith(AndroidJUnit4.class)
 public class TouchExplorerTest {
 
@@ -119,6 +125,9 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     /**
      * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
      * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -161,11 +170,16 @@
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Wait for transiting to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
-        moveEachPointers(mLastEvent, p(10, 10));
-        send(mLastEvent);
+        assertState(STATE_TOUCH_EXPLORING);
+        // Manually construct the next move event. Using moveEachPointers() will batch the move
+        // event which produces zero movement for some reason.
+        float[] x = new float[1];
+        float[] y = new float[1];
+        x[0] = mLastEvent.getX(0) + mTouchSlop;
+        y[0] = mLastEvent.getY(0) + mTouchSlop;
+        send(manyPointerEvent(ACTION_MOVE, x, y));
         goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
         assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
-        assertState(STATE_TOUCH_EXPLORING);
     }
 
     /**
@@ -173,7 +187,8 @@
      * change the coordinates.
      */
     @Test
-    public void testOneFingerMoveWithExtraMoveEvents() {
+    @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+    public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Inject a set of move events that have the same coordinates as the down event.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -181,7 +196,33 @@
         // Wait for transition to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
+        send(mLastEvent);
+        // One more move event with no change.
+        moveEachPointers(mLastEvent, p(0, 0));
+        send(mLastEvent);
+        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
+        assertCapturedEvents(
+                ACTION_HOVER_ENTER,
+                ACTION_HOVER_MOVE,
+                ACTION_HOVER_EXIT);
+    }
+
+    /**
+     * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not
+     * change the coordinates.
+     */
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+    public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {
+        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
+        // Inject a set of move events that have the same coordinates as the down event.
+        moveEachPointers(mLastEvent, p(0, 0));
+        send(mLastEvent);
+        // Wait for transition to touch exploring state.
+        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
+        // Now move for real.
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
         // One more move event with no change.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -242,7 +283,7 @@
         moveEachPointers(mLastEvent, p(0, 0), p(0, 0));
         send(mLastEvent);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
+        moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));
         send(mLastEvent);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
         assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
@@ -251,7 +292,7 @@
     @Test
     public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
         // Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.
         mHandler.fastForward(10);
@@ -277,7 +318,7 @@
 
         // Wait for the finger moving to the second view.
         mHandler.fastForward(oneThirdUserIntentTimeout);
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
 
         // Wait for the finger lifting from the second view.
@@ -402,7 +443,6 @@
         // Manually construct the next move event. Using moveEachPointers() will batch the move
         // event onto the pointer up event which will mean that the move event still has a pointer
         // count of 3.
-        // Todo: refactor to avoid using batching as there is no special reason to do it that way.
         float[] x = new float[2];
         float[] y = new float[2];
         x[0] = mLastEvent.getX(0) + 100;
@@ -734,6 +774,9 @@
         }
     }
 
+    private void moveAtLeastTouchSlop(MotionEvent event) {
+        moveEachPointers(event, p(2 * mTouchSlop, 0));
+    }
     /**
      * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
      * invoked.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 430f600..caa9e7c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -1065,7 +1065,7 @@
                     mMgh.clearAndTransitionToStateDetecting();
                     break;
                 case STATE_ACTIVATED:
-                    if (mMgh.mDetectTripleTap) {
+                    if (mMgh.mDetectSingleFingerTripleTap) {
                         goFromStateIdleTo(STATE_2TAPS);
                         tap();
                     } else {
@@ -1147,7 +1147,7 @@
                 break;
             case STATE_ACTIVATED:
             case STATE_ZOOMED_OUT_FROM_SERVICE:
-                if (mMgh.mDetectTripleTap) {
+                if (mMgh.mDetectSingleFingerTripleTap) {
                     tap();
                     tap();
                     returnToNormalFrom(STATE_ACTIVATED_2TAPS);
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
index 70527ce..44d6760 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -238,7 +238,7 @@
         }
 
         @Override
-        Handler getHandler(Handler.Callback callback) {
+        Handler newHandler(Handler.Callback callback) {
             if (mTestHandler == null) {
                 mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
             }
@@ -250,14 +250,18 @@
             return mTestHandler;
         }
 
+        /**
+         * This override returns the tracker supplied in the constructor.  It does not create a
+         * new one.
+         */
         @Override
-        AnrTimer.CpuTracker getTracker() {
+        AnrTimer.CpuTracker newTracker() {
             return mTracker;
         }
 
         /** For test purposes, always enable the feature. */
         @Override
-        boolean getFeatureEnabled() {
+        boolean isFeatureEnabled() {
             return true;
         }
     }
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 988cd81..feb6bd9 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -34,6 +36,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -49,8 +52,9 @@
         final Context context = InstrumentationRegistry.getContext();
         mBgThread = new HandlerThread("bg thread");
         mBgThread.start();
-        mBatteryStatsService = new BatteryStatsService(context,
-                context.getCacheDir(), new Handler(mBgThread.getLooper()));
+        File systemDir = context.getCacheDir();
+        Handler handler = new Handler(mBgThread.getLooper());
+        mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
     }
 
     @After
@@ -121,4 +125,14 @@
             waitThread.join(1000);
         }
     }
+
+    @Test
+    public void testSavingStatsdAtomPullTimestamp() {
+        mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
+        assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+                .isEqualTo(1234);
+        mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
+        assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+                .isEqualTo(5478);
+    }
 }
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 24a628e..d26d671 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -345,7 +345,7 @@
         assertWithMessage("should not have received intents")
                 .that(getActions(mInjector.mSentIntents)).isEmpty();
         // TODO(b/140868593): should have received a USER_UNLOCK_MSG message as well, but it doesn't
-        // because StorageManager.isUserKeyUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to
+        // because StorageManager.isCeStorageUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to
         // properly fix it, we'd need to move this class to FrameworksMockingServicesTests so we can
         // mock static methods (but moving this class would involve changing the presubmit tests,
         // and the cascade effect goes on...). In fact, a better approach would to not assert the
@@ -648,7 +648,7 @@
         // checking.
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(0))
-                .lockUserKey(anyInt());
+                .lockCeStorage(anyInt());
 
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
                 numerOfUserSwitches, true);
@@ -663,7 +663,7 @@
         mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(1))
-                .lockUserKey(TEST_USER_ID);
+                .lockCeStorage(TEST_USER_ID);
     }
 
     /**
@@ -757,7 +757,7 @@
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
 
         verify(mInjector.mStorageManagerMock, never())
-                .unlockUserKey(eq(TEST_USER_ID), anyInt(), any());
+                .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any());
     }
 
     @Test
@@ -1035,7 +1035,7 @@
         mUserController.finishUserStopped(ussUser, delayedLocking);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
-                .lockUserKey(userId);
+                .lockCeStorage(userId);
     }
 
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
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 9e5a047..3a3dd6e 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
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.ICancellationSignal;
@@ -59,6 +60,7 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import org.junit.Before;
@@ -103,7 +105,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -112,6 +114,8 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     private BiometricManager mBiometricManager;
+    @Mock
+    private LockoutTracker mLockoutTracker;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -236,27 +240,63 @@
         verify(mCallback).onClientFinished(client, true);
     }
 
+    @Test
+    public void authWithNoLockout() throws RemoteException {
+        when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+                LockoutTracker.LOCKOUT_NONE);
+
+        final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+        client.start(mCallback);
+
+        verify(mHal).authenticate(OP_ID);
+    }
+
+    @Test
+    public void authWithLockout() throws RemoteException {
+        when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+                LockoutTracker.LOCKOUT_PERMANENT);
+
+        final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+        client.start(mCallback);
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt());
+        verify(mHal, never()).authenticate(anyInt());
+    }
+
     private FaceAuthenticationClient createClient() throws RemoteException {
         return createClient(2 /* version */, mClientMonitorCallbackConverter,
-                false /* allowBackgroundAuthentication */);
+                false /* allowBackgroundAuthentication */,
+                null /* lockoutTracker */);
     }
 
     private FaceAuthenticationClient createClientWithNullListener() throws RemoteException {
         return createClient(2 /* version */, null /* listener */,
-                true /* allowBackgroundAuthentication */);
+                true /* allowBackgroundAuthentication */,
+                null /* lockoutTracker */);
     }
 
     private FaceAuthenticationClient createClient(int version) throws RemoteException {
         return createClient(version, mClientMonitorCallbackConverter,
-                false /* allowBackgroundAuthentication */);
+                false /* allowBackgroundAuthentication */,
+                null /* lockoutTracker */);
+    }
+
+    private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker)
+            throws RemoteException {
+        return createClient(0 /* version */,
+                mClientMonitorCallbackConverter,
+                true /* allowBackgroundAuthentication */,
+                lockoutTracker);
     }
 
     private FaceAuthenticationClient createClient(int version,
             ClientMonitorCallbackConverter listener,
-            boolean allowBackgroundAuthentication) throws RemoteException {
+            boolean allowBackgroundAuthentication,
+            LockoutTracker lockoutTracker) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
                 .setOpPackageName("test-owner")
                 .setUserId(USER_ID)
@@ -270,7 +310,7 @@
                 false /* restricted */, options, 4 /* cookie */,
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
-                mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication,
+                mUsageStats, lockoutTracker, allowBackgroundAuthentication,
                 null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
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 ade3e82..fbf0e13 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
@@ -87,7 +87,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -170,7 +170,7 @@
     private FaceDetectClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FaceDetectClient(mContext, () -> aidl, mToken,
                 99 /* requestId */, mClientMonitorCallbackConverter,
                 new FaceAuthenticateOptions.Builder()
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 54d116f..128f314 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
@@ -83,7 +83,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -150,7 +150,7 @@
     private FaceEnrollClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
                 USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
                 mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
new file mode 100644
index 0000000..c8bfaa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceGenerateChallengeClientTest {
+    private static final String TAG = "FaceGenerateChallengeClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final long CHALLENGE = 200;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FaceGenerateChallengeClient mClient;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+        doAnswer(invocation -> {
+            mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+            return null;
+        }).when(mSession).generateChallenge();
+    }
+
+    @Test
+    public void generateChallenge() throws RemoteException {
+        createClient(mListener);
+        mClient.start(mCallback);
+
+        verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void generateChallenge_nullListener() {
+        createClient(null);
+        mClient.start(mCallback);
+
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+
+    private void createClient(ClientMonitorCallbackConverter listener) {
+        mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
new file mode 100644
index 0000000..9d0c84e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceGetFeatureClientTest {
+    private static final String TAG = "FaceGetFeatureClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+    private FaceGetFeatureClient mClient;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener,
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature);
+
+        when(mAidlSession.getSession()).thenReturn(mSession);
+        doAnswer(invocation -> {
+            mClient.onFeatureGet(true, new byte[]{
+                    AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)});
+            return null;
+        }).when(mSession).getFeatures();
+    }
+
+    @Test
+    public void getFeature() throws RemoteException {
+        ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class);
+        ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class);
+        mClient.start(mCallback);
+
+        verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(),
+                featureState.capture());
+        assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature));
+        assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
new file mode 100644
index 0000000..1b4c017
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Presubmit
+@SmallTest
+public class FaceInternalCleanupClientTest {
+    private static final String TAG = "FaceInternalCleanupClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Face> mBiometricUtils;
+    @Mock
+    private Map<Integer, Long> mAuthenticatorIds;
+
+    private final List<Face> mEnrolledList = new ArrayList<>();
+    private final int mBiometricId = 1;
+    private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+    private FaceInternalCleanupClient mClient;
+    private List<Integer> mAddedIds;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mEnrolledList.add(mFace);
+        mAddedIds = new ArrayList<>();
+        mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG,
+                SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils,
+                mAuthenticatorIds) {
+            @Override
+            protected void onAddUnknownTemplate(int userId,
+                    @NonNull BiometricAuthenticator.Identifier identifier) {
+                mAddedIds.add(identifier.getBiometricId());
+            }
+        };
+    }
+
+    @Test
+    public void removesUnknownTemplate() throws Exception {
+        final List<Face> templates = List.of(
+                new Face("one", 1, 1),
+                new Face("two", 2, 1)
+        );
+        mClient.start(mCallback);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
+        }
+
+        assertThat(mAddedIds).isEmpty();
+        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
+
+        verify(mSession, times(2)).removeEnrollments(captor.capture());
+        assertThat(captor.getAllValues().stream()
+                .flatMap(x -> Arrays.stream(x).boxed())
+                .collect(Collectors.toList()))
+                .containsExactly(1, 2);
+        verify(mCallback).onClientFinished(eq(mClient), eq(true));
+    }
+
+    @Test
+    public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception {
+        mClient.setFavorHalEnrollments();
+        final List<Face> templates = List.of(
+                new Face("one", 1, 1),
+                new Face("two", 2, 1)
+        );
+        mClient.start(mCallback);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+
+        assertThat(mAddedIds).containsExactly(1, 2);
+        verify(mSession, never()).removeEnrollments(any());
+        verify(mCallback).onClientFinished(eq(mClient), eq(true));
+    }
+
+    @Test
+    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+        final List<Face> templates = List.of(
+                new Face("one", 1, 1),
+                new Face("two", 2, 1),
+                new Face("three", 3, 1)
+        );
+        mClient.start(mCallback);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+
+        // The first template is removed after enumeration
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
+
+        // Simulate finishing the removal of the first template.
+        // |remaining| is 0 because one FaceRemovalClient is associated with only one
+        // biometrics ID.
+        mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0);
+
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+
+        // Simulate finishing the removal of the second template.
+        mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0);
+
+        assertThat(mClient.getUnknownHALTemplates()).isEmpty();
+    }
+
+    @Test
+    public void noUnknownTemplates() throws RemoteException {
+        mClient.start(mCallback);
+        mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mSession, never()).removeEnrollments(any());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
new file mode 100644
index 0000000..8d74fd1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+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.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceInternalEnumerateClientTest {
+    private static final String TAG = "FaceInternalEnumerateClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Face> mBiometricUtils;
+
+    private final int mBiometricId = 1;
+    private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+    private FaceInternalEnumerateClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        final List<Face> enrolled = new ArrayList<>();
+        enrolled.add(mFace);
+        mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
+                TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext);
+    }
+
+    @Test
+    public void internalCleanupClient_noTemplatesRemaining() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(mFace, 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(null, 1);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback, never()).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(null, 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_templatesRemaining() throws RemoteException {
+        final Face identifier = new Face("face", 2, 1);
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(identifier, 1);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback, never()).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_differentIdentifier_noTemplatesRemaining()
+            throws RemoteException {
+        final Face identifier = new Face("face", 2, 1);
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(identifier, 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
index 76a5acc..1d9e933 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -76,7 +76,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private BiometricUtils<Face> mUtils;
     @Mock
@@ -115,7 +115,7 @@
 
     private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FaceRemovalClient(mContext, () -> aidl, mToken,
                 mClientMonitorCallbackConverter, biometricIds, USER_ID,
                 "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
new file mode 100644
index 0000000..dbbd69b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceResetLockoutClientTest {
+    private static final String TAG = "FaceResetLockoutClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    private final byte[] mHardwareAuthToken = new byte[69];
+    @Mock
+    private LockoutCache mLockoutTracker;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+
+    private FaceResetLockoutClient mClient;
+
+    @Before
+    public void setUp() {
+        mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID,
+                mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker,
+                mLockoutResetDispatcher, BIOMETRIC_STRONG);
+
+        when(mAidlSession.getSession()).thenReturn(mSession);
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+    }
+
+    @Test
+    public void testResetLockout_onLockoutCleared() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onLockoutCleared();
+            return null;
+        }).when(mSession).resetLockout(any());
+        mClient.start(mCallback);
+
+        verify(mSession).resetLockout(any());
+        verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1);
+        verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void testResetLockout_onError() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onError(0, 0);
+            return null;
+        }).when(mSession).resetLockout(any());
+        mClient.start(mCallback);
+
+        verify(mSession).resetLockout(any());
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID,
+                BIOMETRIC_STRONG, -1);
+        verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID,
+                LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
new file mode 100644
index 0000000..fb5502a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
@@ -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.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceRevokeChallengeClientTest {
+    private static final String TAG = "FaceRevokeChallengeClientTest";
+    private static final long CHALLENGE = 200L;
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FaceRevokeChallengeClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG,
+                SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+    }
+
+    @Test
+    public void revokeChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE);
+            return null;
+        }).when(mSession).revokeChallenge(CHALLENGE);
+        mClient.start(mCallback);
+
+        verify(mSession).revokeChallenge(CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
new file mode 100644
index 0000000..eb8cc9c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceSetFeatureClientTest {
+    private static final String TAG = "FaceSetFeatureClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+    private final boolean mEnabled = true;
+    private final byte[] mHardwareAuthToken = new byte[69];
+    private FaceSetFeatureClient mClient;
+    TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID,
+                TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled,
+                mHardwareAuthToken);
+    }
+
+    @Test
+    public void setFeature_onFeatureSet() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onFeatureSet(true);
+            return null;
+        }).when(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled));
+        mClient.start(mCallback);
+
+        verify(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+                eq(mEnabled));
+        verify(mListener).onFeatureSet(true, mFeature);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void setFeature_onError() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onError(0, 0);
+            return null;
+        }).when(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+                eq(mEnabled));
+        mClient.start(mCallback);
+
+        verify(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+                eq(mEnabled));
+        verify(mListener).onFeatureSet(false, mFeature);
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+}
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 be9f52e..7a293e8 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
@@ -74,7 +74,7 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
-    private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
     @Mock
@@ -94,7 +94,7 @@
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
-    private Sensor.HalSessionCallback mHalCallback;
+    private AidlResponseHandler mHalCallback;
 
     @Before
     public void setUp() {
@@ -111,10 +111,9 @@
                 mBiometricService,
                 () -> USER_ID,
                 mUserSwitchCallback);
-        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
-                TAG, mScheduler, SENSOR_ID,
-                USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mHalSessionCallback);
+        mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                mHardwareUnavailableCallback);
     }
 
     @Test
@@ -153,11 +152,11 @@
         sensorProps.commonProps.sensorId = 1;
         final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
                 sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                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);
+        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
+                null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
 
         mScheduler.reset();
 
@@ -181,7 +180,7 @@
         sensorProps.commonProps.sensorId = 1;
         final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
                 sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
                 sensorProps.sensorType, sensorProps.supportsDetectInteraction,
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
         final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
new file mode 100644
index 0000000..9a40e8a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
+
+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.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.EnrollmentType;
+import android.hardware.biometrics.face.Feature;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricsFace mSession;
+    @Mock
+    FaceManager mFaceManager;
+    @Mock
+    private AidlResponseHandler mAidlResponseHandler;
+    @Mock
+    private HardwareAuthToken mHardwareAuthToken;
+    @Mock
+    private Clock mClock;
+
+    private final long mChallenge = 100L;
+    private AidlToHidlAdapter mAidlToHidlAdapter;
+    private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
+    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
+    private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
+
+    @Before
+    public void setUp() throws RemoteException {
+        TestableContext testableContext = new TestableContext(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        testableContext.addMockSystemService(FaceManager.class, mFaceManager);
+        mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
+                mAidlResponseHandler, mClock);
+        mHardwareAuthToken.timestamp = new Timestamp();
+        mHardwareAuthToken.mac = new byte[10];
+        final OptionalUint64 result = new OptionalUint64();
+        result.status = Status.OK;
+        result.value = mChallenge;
+
+        when(mSession.generateChallenge(anyInt())).thenReturn(result);
+        when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
+    }
+
+    @Test
+    public void testGenerateChallengeCache() throws RemoteException {
+        verify(mSession).setCallback(any());
+
+        final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
+
+        mAidlToHidlAdapter.generateChallenge();
+
+        verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+        verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
+        assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
+
+        forwardTime(10 /* seconds */);
+        mAidlToHidlAdapter.generateChallenge();
+        forwardTime(20 /* seconds */);
+        mAidlToHidlAdapter.generateChallenge();
+
+        //Confirms that the challenge is cached and the hal method is not called again
+        verifyNoMoreInteractions(mSession);
+        verify(mAidlResponseHandler, times(3))
+                .onChallengeGenerated(mChallenge);
+
+        forwardTime(60 /* seconds */);
+        mAidlToHidlAdapter.generateChallenge();
+
+        //HAL method called after challenge has timed out
+        verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+    }
+
+    @Test
+    public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
+        for (int i = 0; i < 3; i++) {
+            mAidlToHidlAdapter.generateChallenge();
+            forwardTime(10 /* seconds */);
+        }
+        for (int i = 0; i < 3; i++) {
+            mAidlToHidlAdapter.revokeChallenge(0);
+            forwardTime((i + 1) * 10 /* seconds */);
+        }
+
+        verify(mSession).revokeChallenge();
+    }
+
+    @Test
+    public void testRevokeChallenge_timeout() throws RemoteException {
+        mAidlToHidlAdapter.generateChallenge();
+        mAidlToHidlAdapter.generateChallenge();
+        forwardTime(700);
+        mAidlToHidlAdapter.generateChallenge();
+        mAidlToHidlAdapter.revokeChallenge(0);
+
+        verify(mSession).revokeChallenge();
+    }
+
+    @Test
+    public void testEnroll() throws RemoteException {
+        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
+                EnrollmentType.DEFAULT, mFeatures,
+                null /* previewSurface */);
+        ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+        verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
+
+        ArrayList<Integer> features = featureCaptor.getValue();
+
+        assertThat(features).containsExactly(
+                AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0]));
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testAuthenticate() throws RemoteException {
+        final int operationId = 2;
+        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+        verify(mSession).authenticate(operationId);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testDetectInteraction() throws RemoteException {
+        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+        verify(mSession).authenticate(0);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testEnumerateEnrollments() throws RemoteException {
+        mAidlToHidlAdapter.enumerateEnrollments();
+
+        verify(mSession).enumerate();
+    }
+
+    @Test
+    public void testRemoveEnrollment() throws RemoteException {
+        final int[] enrollments = new int[]{1};
+        mAidlToHidlAdapter.removeEnrollments(enrollments);
+
+        verify(mSession).remove(enrollments[0]);
+    }
+
+    @Test
+    public void testGetFeatures_onResultSuccess() throws RemoteException {
+        final OptionalBool result = new OptionalBool();
+        result.status = Status.OK;
+        result.value = true;
+        ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+        mAidlToHidlAdapter.setFeature(mFeature);
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+        assertThat(featureRetrieved.getValue()[0]).isEqualTo(
+                AidlConversionUtils.convertFrameworkToAidlFeature(mFeature));
+    }
+
+    @Test
+    public void testGetFeatures_onResultFailed() throws RemoteException {
+        final OptionalBool result = new OptionalBool();
+        result.status = Status.OK;
+        result.value = false;
+        ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+        mAidlToHidlAdapter.setFeature(mFeature);
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+        assertThat(featureRetrieved.getValue().length).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetFeatures_onStatusFailed() throws RemoteException {
+        final OptionalBool result = new OptionalBool();
+        result.status = Status.INTERNAL_ERROR;
+        result.value = false;
+
+        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+        mAidlToHidlAdapter.setFeature(mFeature);
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+        verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0);
+    }
+
+    @Test
+    public void testGetFeatures_featureNotSet() throws RemoteException {
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession, never()).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+    }
+
+    @Test
+    public void testSetFeatureSuccessful() throws RemoteException {
+        byte feature = Feature.REQUIRE_ATTENTION;
+        boolean enabled = true;
+
+        when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
+
+        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+        verify(mAidlResponseHandler).onFeatureSet(feature);
+    }
+
+    @Test
+    public void testSetFeatureFailed() throws RemoteException {
+        byte feature = Feature.REQUIRE_ATTENTION;
+        boolean enabled = true;
+
+        when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
+                .thenReturn(Status.INTERNAL_ERROR);
+
+        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+        verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
+                0 /* vendorCode */);
+    }
+
+    @Test
+    public void testGetAuthenticatorId() throws RemoteException {
+        final long authenticatorId = 2L;
+        final OptionalUint64 result = new OptionalUint64();
+        result.status = Status.OK;
+        result.value = authenticatorId;
+
+        when(mSession.getAuthenticatorId()).thenReturn(result);
+
+        mAidlToHidlAdapter.getAuthenticatorId();
+
+        verify(mSession).getAuthenticatorId();
+        verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
+    }
+
+    @Test
+    public void testResetLockout() throws RemoteException {
+        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+        ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+        verify(mSession).resetLockout(hatCaptor.capture());
+
+        assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken));
+    }
+
+    private ArrayList<Byte> processHAT(HardwareAuthToken hat) {
+        ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+        for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+            hardwareAuthToken.add(b);
+        }
+        return hardwareAuthToken;
+    }
+
+    private void forwardTime(long seconds) {
+        when(mClock.millis()).thenReturn(seconds * 1000);
+    }
+}
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 8a11e31..79a528c 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
@@ -53,11 +53,15 @@
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 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.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;
@@ -66,6 +70,7 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -102,6 +107,9 @@
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private ISession mHal;
@@ -124,7 +132,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -135,6 +143,8 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     private Clock mClock;
+    @Mock
+    private LockoutTracker mLockoutTracker;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -425,37 +435,65 @@
         verify(mCallback).onClientFinished(client, true);
     }
 
+    @Test
+    public void testLockoutTracker_authSuccess() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1 /* version */,
+                true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+                mLockoutTracker);
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+        verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+        verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testLockoutTracker_authFailed() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1 /* version */,
+                true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+                mLockoutTracker);
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+        verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt());
+        verify(mLockoutTracker).addFailedAttemptForUser(USER_ID);
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
-                mClientMonitorCallbackConverter);
+                mClientMonitorCallbackConverter, null);
     }
 
     private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()
             throws RemoteException {
         return createClient(100 /* version */, false /* allowBackgroundAuthentication */,
-                mClientMonitorCallbackConverter);
+                mClientMonitorCallbackConverter, null);
     }
 
     private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
         return createClient(version, true /* allowBackgroundAuthentication */,
-                mClientMonitorCallbackConverter);
+                mClientMonitorCallbackConverter, null);
     }
 
     private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
-                null /* listener */);
+                null, /* listener */null);
     }
 
     private FingerprintAuthenticationClient createClient(int version,
-            boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener)
+            boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener,
+            LockoutTracker lockoutTracker)
             throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
                 .setOpPackageName("test-owner")
-                .setUserId(5)
-                .setSensorId(9)
+                .setUserId(USER_ID)
+                .setSensorId(SENSOR_ID)
                 .build();
         return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
                 REQUEST_ID, listener, OP_ID,
@@ -463,10 +501,11 @@
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
-                null /* taskStackListener */, null /* lockoutCache */,
+                null /* taskStackListener */,
                 mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
                 mSensorProps,
-                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
+                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+                lockoutTracker) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
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 78d3a9d..a467c84 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
@@ -83,7 +83,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -150,7 +150,7 @@
 
     @Test
     public void testWhenListenerIsNull() {
-        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler);
         final FingerprintDetectClient client =  new FingerprintDetectClient(mContext, () -> aidl,
                 mToken, 6 /* requestId */, null /* listener */,
                 new FingerprintAuthenticateOptions.Builder()
@@ -173,7 +173,7 @@
     private FingerprintDetectClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FingerprintDetectClient(mContext, () -> aidl, mToken,
                 6 /* requestId */, mClientMonitorCallbackConverter,
                 new FingerprintAuthenticateOptions.Builder()
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 ef25380..c7eb1db 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
@@ -102,7 +102,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private Probe mLuxProbe;
     @Captor
@@ -291,7 +291,7 @@
     private FingerprintEnrollClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID,
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
new file mode 100644
index 0000000..8409619
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintGenerateChallengeClientTest {
+    private static final String TAG = "FingerprintGenerateChallengeClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final long CHALLENGE = 200;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FingerprintGenerateChallengeClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken,
+                mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+    }
+
+    @Test
+    public void generateChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+            return null;
+        }).when(mSession).generateChallenge();
+        mClient.start(mCallback);
+
+        verify(mSession).generateChallenge();
+        verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index 5806443..c9482ce 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.testing.TestableContext;
 
@@ -41,7 +42,6 @@
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -70,9 +70,9 @@
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
 
     @Mock
-    private AidlSession mAidlSession;
+    ISession mSession;
     @Mock
-    private ISession mSession;
+    private AidlSession mAidlSession;
     @Mock
     private BiometricLogger mLogger;
     @Mock
@@ -87,15 +87,16 @@
 
     @Before
     public void setup() {
-        when(mAidlSession.getSession()).thenReturn(mSession);
         mAddedIds = new ArrayList<>();
+
+        when(mAidlSession.getSession()).thenReturn(mSession);
     }
 
-    @Ignore("TODO(b/229015801): verify cleanup behavior")
     @Test
     public void removesUnknownTemplate() throws Exception {
         mClient = createClient();
 
+        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
         final List<Fingerprint> templates = List.of(
                 new Fingerprint("one", 1, 1),
                 new Fingerprint("two", 2, 1)
@@ -108,8 +109,8 @@
             mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
         }
 
+        verify(mSession).enumerateEnrollments();
         assertThat(mAddedIds).isEmpty();
-        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
         verify(mSession, times(2)).removeEnrollments(captor.capture());
         assertThat(captor.getAllValues().stream()
                 .flatMap(x -> Arrays.stream(x).boxed())
@@ -132,13 +133,15 @@
             mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
         }
 
+        verify(mSession).enumerateEnrollments();
         assertThat(mAddedIds).containsExactly(1, 2);
         verify(mSession, never()).removeEnrollments(any());
         verify(mCallback).onClientFinished(eq(mClient), eq(true));
     }
 
     @Test
-    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled()
+            throws RemoteException {
         mClient = createClient();
 
         final List<Fingerprint> templates = List.of(
@@ -150,6 +153,8 @@
         for (int i = templates.size() - 1; i >= 0; i--) {
             mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
         }
+
+        verify(mSession).enumerateEnrollments();
         // The first template is removed after enumeration
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
new file mode 100644
index 0000000..723f916
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Presubmit
+@SmallTest
+public class FingerprintInternalEnumerateClientTest {
+    private static final String TAG = "FingerprintInternalEnumerateClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+
+    private FingerprintInternalEnumerateClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        List<Fingerprint> enrolled = new ArrayList<>();
+        enrolled.add(new Fingerprint("one", 1, 1));
+        mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
+                USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger,
+                mBiometricContext);
+    }
+
+    @Test
+    public void internalEnumerate_unknownTemplates() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1);
+            mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().stream()
+                .flatMap(x -> Stream.of(x.getBiometricId()))
+                .collect(Collectors.toList())).containsExactly(2, 3);
+        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalEnumerate_noUnknownTemplates() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
new file mode 100644
index 0000000..64f07e2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FingerprintRemovalClientTest {
+    private static final String TAG = "FingerprintRemovalClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+    @Mock
+    private Map<Integer, Long> mAuthenticatorIds;
+
+    private FingerprintRemovalClient mClient;
+    private int[] mBiometricIds = new int[]{1, 2};
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener,
+                mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID,
+                mBiometricLogger, mBiometricContext, mAuthenticatorIds);
+    }
+
+    @Test
+    public void removalMultipleFingerprints() throws RemoteException {
+        when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(
+                List.of(new Fingerprint("three", 3, 1)));
+        doAnswer(invocation -> {
+            mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+            mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+            return null;
+        }).when(mSession).removeEnrollments(mBiometricIds);
+        mClient.start(mCallback);
+
+        verify(mSession).removeEnrollments(mBiometricIds);
+        verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+                eq(USER_ID), anyInt());
+        verifyNoMoreInteractions(mAuthenticatorIds);
+        verify(mListener, times(2)).onRemoved(any(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void removeFingerprint_nullIdentifier() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onRemoved(null, 0);
+            return null;
+        }).when(mSession).removeEnrollments(mBiometricIds);
+        mClient.start(mCallback);
+
+        verify(mSession).removeEnrollments(mBiometricIds);
+        verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt());
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+
+    @Test
+    public void removeFingerprints_noFingerprintEnrolled() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+            mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+            return null;
+        }).when(mSession).removeEnrollments(mBiometricIds);
+        when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>());
+
+        mClient.start(mCallback);
+
+        verify(mSession).removeEnrollments(mBiometricIds);
+        verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+                eq(USER_ID), anyInt());
+        verify(mAuthenticatorIds).put(USER_ID, 0L);
+        verify(mListener, times(2)).onRemoved(any(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
new file mode 100644
index 0000000..a4746de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+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.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintResetLockoutClientTest {
+    private static final String TAG = "FingerprintResetLockoutClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private LockoutTracker mLockoutTracker;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+
+    private FingerprintResetLockoutClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG,
+                SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69],
+                mLockoutTracker, mLockoutResetDispatcher,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void resetLockout_onLockoutCleared() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onLockoutCleared();
+            return null;
+        }).when(mSession).resetLockout(any());
+        mClient.start(mCallback);
+
+        verify(mSession).resetLockout(any());
+        verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID),
+                eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
new file mode 100644
index 0000000..f19b2f7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintRevokeChallengeClientTest {
+    private static final String TAG = "FingerprintRevokeChallengeClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final long CHALLENGE = 200;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FingerprintRevokeChallengeClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken,
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+    }
+
+    @Test
+    public void revokeChallenge_sameChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeRevoked(CHALLENGE);
+            return null;
+        }).when(mSession).revokeChallenge(CHALLENGE);
+        mClient.start(mCallback);
+
+        verify(mSession).revokeChallenge(CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void revokeChallenge_differentChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeRevoked(CHALLENGE + 1);
+            return null;
+        }).when(mSession).revokeChallenge(CHALLENGE);
+        mClient.start(mCallback);
+
+        verify(mSession).revokeChallenge(CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+}
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 15d7601..4102600 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
@@ -75,7 +75,7 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
-    private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
     @Mock
@@ -97,7 +97,7 @@
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
-    private Sensor.HalSessionCallback mHalCallback;
+    private AidlResponseHandler mHalCallback;
 
     @Before
     public void setUp() {
@@ -113,10 +113,9 @@
                 mBiometricService,
                 () -> USER_ID,
                 mUserSwitchCallback);
-        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
-                TAG, mScheduler, SENSOR_ID,
-                USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mHalSessionCallback);
+        mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                mHardwareUnavailableCallback);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
new file mode 100644
index 0000000..b78ba82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
@@ -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.server.biometrics.sensors.fingerprint.hidl;
+
+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.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricsFingerprint mSession;
+    @Mock
+    private AidlResponseHandler mAidlResponseHandler;
+    @Mock
+    private HardwareAuthToken mHardwareAuthToken;
+
+    private final long mChallenge = 100L;
+    private final int mUserId = 0;
+    private AidlToHidlAdapter mAidlToHidlAdapter;
+
+    @Before
+    public void setUp() {
+        mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+                mAidlResponseHandler);
+        mHardwareAuthToken.timestamp = new Timestamp();
+        mHardwareAuthToken.mac = new byte[10];
+    }
+
+    @Test
+    public void testGenerateChallenge() throws RemoteException {
+        when(mSession.preEnroll()).thenReturn(mChallenge);
+        mAidlToHidlAdapter.generateChallenge();
+
+        verify(mSession).preEnroll();
+        verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
+    }
+
+    @Test
+    public void testRevokeChallenge() throws RemoteException {
+        mAidlToHidlAdapter.revokeChallenge(mChallenge);
+
+        verify(mSession).postEnroll();
+        verify(mAidlResponseHandler).onChallengeRevoked(0L);
+    }
+
+    @Test
+    public void testEnroll() throws RemoteException {
+        final ICancellationSignal cancellationSignal =
+                mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+
+        verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testAuthenticate() throws RemoteException {
+        final int operationId = 2;
+        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+        verify(mSession).authenticate(operationId, mUserId);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testDetectInteraction() throws RemoteException {
+        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+        verify(mSession).authenticate(0 /* operationId */, mUserId);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testEnumerateEnrollments() throws RemoteException {
+        mAidlToHidlAdapter.enumerateEnrollments();
+
+        verify(mSession).enumerate();
+    }
+
+    @Test
+    public void testRemoveEnrollment() throws RemoteException {
+        final int[] enrollmentIds = new int[]{1};
+        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+        verify(mSession).remove(mUserId, enrollmentIds[0]);
+    }
+
+    @Test
+    public void testRemoveMultipleEnrollments() throws RemoteException {
+        final int[] enrollmentIds = new int[]{1, 2};
+        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+        verify(mSession).remove(mUserId, 0);
+    }
+
+    @Test
+    public void testResetLockout() throws RemoteException {
+        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+        verify(mAidlResponseHandler).onLockoutCleared();
+    }
+}
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 e8cbcf9..a3d415e 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
@@ -337,11 +337,7 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
-        mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
-        mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
-        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM);
+        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
 
         doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index e7777f7..78bf9b0 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -16,13 +16,14 @@
 
 package com.android.server.contentcapture;
 
+import static android.view.contentprotection.flags.Flags.FLAG_PARSE_GROUPS_CONFIG_ENABLED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
@@ -33,6 +34,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.contentcapture.ContentCaptureServiceInfo;
 import android.view.contentcapture.ContentCaptureEvent;
 
@@ -41,7 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
 import com.android.server.contentprotection.ContentProtectionConsentManager;
 import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.pm.UserManagerInternal;
@@ -56,6 +58,9 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Test for {@link ContentCaptureManagerService}.
  *
@@ -84,9 +89,11 @@
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock private UserManagerInternal mMockUserManagerInternal;
 
-    @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+    @Mock private ContentProtectionAllowlistManager mMockContentProtectionAllowlistManager;
 
     @Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
 
@@ -96,7 +103,11 @@
 
     private boolean mDevCfgEnableContentProtectionReceiver;
 
-    private int mContentProtectionBlocklistManagersCreated;
+    private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a"));
+
+    private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+
+    private int mContentProtectionAllowlistManagersCreated;
 
     private int mContentProtectionServiceInfosCreated;
 
@@ -120,10 +131,10 @@
 
     @Test
     public void constructor_contentProtection_flagDisabled_noManagers() {
-        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
         assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
-        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
         verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
@@ -133,10 +144,10 @@
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
-        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
         assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
-        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
         verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
@@ -146,10 +157,10 @@
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
-        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
         assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
-        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
         verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
@@ -159,26 +170,13 @@
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
-        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
         assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
-        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
         verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-        mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
-        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
-
-        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
-
-        verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
-    }
-
-    @Test
     public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -189,13 +187,13 @@
 
         assertThat(actual).isNull();
         verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
         when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
-        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -227,13 +225,13 @@
         assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
         assertThat(actual.whitelistedComponents).isNull();
         verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
         when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
-        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
         mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
@@ -261,7 +259,7 @@
 
         assertThat(actual).isFalse();
         verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
@@ -275,13 +273,13 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
-        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
     }
 
     @Test
     public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
         when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
-        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -305,7 +303,7 @@
 
         assertThat(actual).isTrue();
         verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
@@ -319,7 +317,7 @@
 
         assertThat(actual).isFalse();
         verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
@@ -333,13 +331,13 @@
                         USER_ID, COMPONENT_NAME);
 
         assertThat(actual).isFalse();
-        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
     }
 
     @Test
     public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
         when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
-        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -363,22 +361,36 @@
 
         assertThat(actual).isTrue();
         verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
-    public void isContentProtectionReceiverEnabled_withoutManagers() {
+    public void isContentProtectionReceiverEnabled_true() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_false_withoutManagers() {
         boolean actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
         verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
-    public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+    public void isContentProtectionReceiverEnabled_false_disabledWithFlag() {
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
         mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
@@ -389,7 +401,23 @@
 
         assertThat(actual).isFalse();
         verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
-        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_false_emptyGroups() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mDevCfgContentProtectionRequiredGroups = Collections.emptyList();
+        mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
@@ -436,12 +464,92 @@
         verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS);
     }
 
+    @Test
+    public void parseContentProtectionGroupsConfig_disabled_null() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_disabled_empty() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_disabled_notEmpty() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_null() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_empty() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_singleValue() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a"))
+                .isEqualTo(List.of(List.of("a")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_multipleValues() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a,b"))
+                .isEqualTo(List.of(List.of("a", "b")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_multipleGroups() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e"))
+                .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_emptyValues() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;"))
+                .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e")));
+    }
+
     private class TestContentCaptureManagerService extends ContentCaptureManagerService {
 
         TestContentCaptureManagerService() {
             super(sContext);
             this.mDevCfgEnableContentProtectionReceiver =
                     ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+            this.mDevCfgContentProtectionRequiredGroups =
+                    ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups;
+            this.mDevCfgContentProtectionOptionalGroups =
+                    ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups;
         }
 
         @Override
@@ -450,9 +558,9 @@
         }
 
         @Override
-        protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
-            mContentProtectionBlocklistManagersCreated++;
-            return mMockContentProtectionBlocklistManager;
+        protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+            mContentProtectionAllowlistManagersCreated++;
+            return mMockContentProtectionAllowlistManager;
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
new file mode 100644
index 0000000..6767a85
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionAllowlistManager}.
+ *
+ * <p>Run with: {@code atest FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionAllowlistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionAllowlistManagerTest {
+
+    private static final String PACKAGE_NAME = "com.test.package.name";
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
+
+    @Before
+    public void setup() {
+        mContentProtectionAllowlistManager = new ContentProtectionAllowlistManager();
+    }
+
+    @Test
+    public void isAllowed() {
+        boolean actual = mContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
deleted file mode 100644
index ba9956a..0000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
+++ /dev/null
@@ -1,236 +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.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test for {@link ContentProtectionBlocklistManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:
- * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionBlocklistManagerTest {
-
-    private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
-
-    private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
-
-    private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
-
-    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
-            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
-    private static final PackageInfo PACKAGE_INFO = new PackageInfo();
-
-    private static final List<String> LINES =
-            ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
-
-    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
-
-    private final List<String> mReadRawFiles = new ArrayList<>();
-
-    private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
-
-    @Before
-    public void setup() {
-        mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
-    }
-
-    @Test
-    public void isAllowed_blocklistNotLoaded() {
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-        assertThat(mReadRawFiles).isEmpty();
-        verifyZeroInteractions(mMockContentProtectionPackageManager);
-    }
-
-    @Test
-    public void isAllowed_inBlocklist() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-        verifyZeroInteractions(mMockContentProtectionPackageManager);
-    }
-
-    @Test
-    public void isAllowed_packageInfoNotFound() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
-                .thenReturn(null);
-
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-        verify(mMockContentProtectionPackageManager, never())
-                .hasRequestedInternetPermissions(any());
-        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
-        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
-    }
-
-    @Test
-    public void isAllowed_notRequestedInternet() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
-                .thenReturn(PACKAGE_INFO);
-        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
-                .thenReturn(false);
-
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
-        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
-    }
-
-    @Test
-    public void isAllowed_systemApp() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
-                .thenReturn(PACKAGE_INFO);
-        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
-                .thenReturn(true);
-        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
-
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
-    }
-
-    @Test
-    public void isAllowed_updatedSystemApp() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
-                .thenReturn(PACKAGE_INFO);
-        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
-                .thenReturn(true);
-        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
-        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
-                .thenReturn(true);
-
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-    }
-
-    @Test
-    public void isAllowed_allowed() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
-                .thenReturn(PACKAGE_INFO);
-        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
-                .thenReturn(true);
-        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
-        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
-                .thenReturn(false);
-
-        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
-        assertThat(actual).isTrue();
-    }
-
-    @Test
-    public void updateBlocklist_negativeSize() {
-        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
-        assertThat(mReadRawFiles).isEmpty();
-
-        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
-    }
-
-    @Test
-    public void updateBlocklist_zeroSize() {
-        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
-        assertThat(mReadRawFiles).isEmpty();
-
-        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
-    }
-
-    @Test
-    public void updateBlocklist_positiveSize_belowTotal() {
-        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
-        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
-        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
-        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
-        verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
-    }
-
-    @Test
-    public void updateBlocklist_positiveSize_aboveTotal() {
-        mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
-        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
-        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
-        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
-        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
-    }
-
-    private final class TestContentProtectionBlocklistManager
-            extends ContentProtectionBlocklistManager {
-
-        TestContentProtectionBlocklistManager() {
-            super(mMockContentProtectionPackageManager);
-        }
-
-        @Override
-        protected List<String> readLinesFromRawFile(@NonNull String filename) {
-            mReadRawFiles.add(filename);
-            return LINES;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
deleted file mode 100644
index 7d45ea4..0000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.Manifest.permission;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.testing.TestableContext;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Test for {@link ContentProtectionPackageManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionPackageManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionPackageManagerTest {
-    private static final String PACKAGE_NAME = "PACKAGE_NAME";
-
-    private static final PackageInfo EMPTY_PACKAGE_INFO = new PackageInfo();
-
-    private static final PackageInfo SYSTEM_APP_PACKAGE_INFO = createSystemAppPackageInfo();
-
-    private static final PackageInfo UPDATED_SYSTEM_APP_PACKAGE_INFO =
-            createUpdatedSystemAppPackageInfo();
-
-    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Rule
-    public final TestableContext mContext =
-            new TestableContext(ApplicationProvider.getApplicationContext());
-
-    @Mock private PackageManager mMockPackageManager;
-
-    private ContentProtectionPackageManager mContentProtectionPackageManager;
-
-    @Before
-    public void setup() {
-        mContext.setMockPackageManager(mMockPackageManager);
-        mContentProtectionPackageManager = new ContentProtectionPackageManager(mContext);
-    }
-
-    @Test
-    public void getPackageInfo_found() throws Exception {
-        PackageInfo expected = createPackageInfo(/* flags= */ 0);
-        when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
-                .thenReturn(expected);
-
-        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void getPackageInfo_notFound() throws Exception {
-        when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
-                .thenThrow(new NameNotFoundException());
-
-        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
-        assertThat(actual).isNull();
-    }
-
-    @Test
-    public void getPackageInfo_null() {
-        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
-        assertThat(actual).isNull();
-    }
-
-    @Test
-    public void isSystemApp_true() {
-        boolean actual = mContentProtectionPackageManager.isSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
-        assertThat(actual).isTrue();
-    }
-
-    @Test
-    public void isSystemApp_false() {
-        boolean actual =
-                mContentProtectionPackageManager.isSystemApp(UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
-        assertThat(actual).isFalse();
-    }
-
-    @Test
-    public void isSystemApp_noApplicationInfo() {
-        boolean actual = mContentProtectionPackageManager.isSystemApp(EMPTY_PACKAGE_INFO);
-
-        assertThat(actual).isFalse();
-    }
-
-    @Test
-    public void isUpdatedSystemApp_true() {
-        boolean actual =
-                mContentProtectionPackageManager.isUpdatedSystemApp(
-                        UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
-        assertThat(actual).isTrue();
-    }
-
-    @Test
-    public void isUpdatedSystemApp_false() {
-        boolean actual =
-                mContentProtectionPackageManager.isUpdatedSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
-        assertThat(actual).isFalse();
-    }
-
-    @Test
-    public void isUpdatedSystemApp_noApplicationInfo() {
-        boolean actual = mContentProtectionPackageManager.isUpdatedSystemApp(EMPTY_PACKAGE_INFO);
-
-        assertThat(actual).isFalse();
-    }
-
-    @Test
-    public void hasRequestedInternetPermissions_true() {
-        PackageInfo packageInfo = createPackageInfo(new String[] {permission.INTERNET});
-
-        boolean actual =
-                mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
-        assertThat(actual).isTrue();
-    }
-
-    @Test
-    public void hasRequestedInternetPermissions_false() {
-        PackageInfo packageInfo = createPackageInfo(new String[] {permission.ACCESS_FINE_LOCATION});
-
-        boolean actual =
-                mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
-        assertThat(actual).isFalse();
-    }
-
-    @Test
-    public void hasRequestedInternetPermissions_noRequestedPermissions() {
-        boolean actual =
-                mContentProtectionPackageManager.hasRequestedInternetPermissions(
-                        EMPTY_PACKAGE_INFO);
-
-        assertThat(actual).isFalse();
-    }
-
-    private static PackageInfo createSystemAppPackageInfo() {
-        return createPackageInfo(ApplicationInfo.FLAG_SYSTEM);
-    }
-
-    private static PackageInfo createUpdatedSystemAppPackageInfo() {
-        return createPackageInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
-    }
-
-    private static PackageInfo createPackageInfo(int flags) {
-        return createPackageInfo(flags, /* requestedPermissions= */ new String[0]);
-    }
-
-    private static PackageInfo createPackageInfo(String[] requestedPermissions) {
-        return createPackageInfo(/* flags= */ 0, requestedPermissions);
-    }
-
-    private static PackageInfo createPackageInfo(int flags, String[] requestedPermissions) {
-        PackageInfo packageInfo = new PackageInfo();
-        packageInfo.packageName = PACKAGE_NAME;
-        packageInfo.applicationInfo = new ApplicationInfo();
-        packageInfo.applicationInfo.packageName = PACKAGE_NAME;
-        packageInfo.applicationInfo.flags = flags;
-        packageInfo.requestedPermissions = requestedPermissions;
-        return packageInfo;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 9b32a80..de3cfbf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -54,8 +54,8 @@
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 16fdfb1..76aa40c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -77,7 +77,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 7d73563..7dcfc88 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -284,6 +284,7 @@
         assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
     }
 
+    @FlakyTest(bugId = 297949293)
     @Test
     public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException {
         // Create a provider and a service without an initial base state.
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 6bfd93b..4bb7d63 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -4,6 +4,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.job.JobStore.JOB_FILE_SPLIT_PREFIX;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -46,6 +48,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.nio.file.Files;
 import java.time.Clock;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
@@ -209,6 +212,43 @@
         assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
     }
 
+    @Test
+    public void testSkipExtraFiles() throws Exception {
+        setUseSplitFiles(true);
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        final File rootDir = new File(mTestContext.getFilesDir(), "system/job");
+        final File file1 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml");
+        final File file2 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid2 + ".xml");
+
+        Files.copy(file1.toPath(),
+                new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml.bak").toPath());
+        Files.copy(file1.toPath(), new File(rootDir, "random.xml").toPath());
+        Files.copy(file2.toPath(),
+                new File(rootDir, "blah" + JOB_FILE_SPLIT_PREFIX + uid1 + ".xml").toPath());
+
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
+    }
+
     /**
      * Test that dynamic constraints aren't written to disk.
      */
@@ -254,22 +294,22 @@
         file = new File(mTestContext.getFilesDir(), "10000");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX);
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "text.xml");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + ".xml");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "-10123.xml");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "1.xml");
         assertEquals(1, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "101023.xml");
         assertEquals(101023, JobStore.extractUidFromJobFileName(file));
     }
 
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 fe2ac17..f5d50d1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -304,17 +304,17 @@
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
-            mStorageManager.unlockUserKey(/* userId= */ (int) args[0],
+            mStorageManager.unlockCeStorage(/* userId= */ (int) args[0],
                     /* secret= */ (byte[]) args[2]);
             return null;
-        }).when(sm).unlockUserKey(anyInt(), anyInt(), any());
+        }).when(sm).unlockCeStorage(anyInt(), anyInt(), any());
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
-            mStorageManager.setUserKeyProtection(/* userId= */ (int) args[0],
+            mStorageManager.setCeStorageProtection(/* userId= */ (int) args[0],
                     /* secret= */ (byte[]) args[1]);
             return null;
-        }).when(sm).setUserKeyProtection(anyInt(), any());
+        }).when(sm).setCeStorageProtection(anyInt(), any());
 
         return sm;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
index 91f3fed..c08ad13 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
@@ -24,7 +24,7 @@
 
     private final ArrayMap<Integer, byte[]> mUserSecrets = new ArrayMap<>();
 
-    public void setUserKeyProtection(int userId, byte[] secret) {
+    public void setCeStorageProtection(int userId, byte[] secret) {
         assertThat(mUserSecrets).doesNotContainKey(userId);
         mUserSecrets.put(userId, secret);
     }
@@ -35,7 +35,7 @@
         return secret;
     }
 
-    public void unlockUserKey(int userId, byte[] secret) {
+    public void unlockCeStorage(int userId, byte[] secret) {
         assertThat(mUserSecrets.get(userId)).isEqualTo(secret);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index a029db9..fa3c7a4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -22,7 +22,7 @@
 
 import android.content.Context;
 
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 23f14f8..02b86db 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -53,8 +53,8 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 37a6d22..eca19c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -255,7 +255,7 @@
     public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
         initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
@@ -267,7 +267,7 @@
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
@@ -285,39 +285,39 @@
     @Test
     public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
     public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
     public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         var captor = ArgumentCaptor.forClass(byte[].class);
         verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
         var value = captor.getValue();
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
     }
 
     @Test
     public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         var captor = ArgumentCaptor.forClass(byte[].class);
         verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
         var value = captor.getValue();
         mService.clearAuthSecret();
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
index 75d71da..06f117b 100644
--- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
@@ -38,9 +38,10 @@
 public class BluetoothRouteControllerTest {
 
     private final BluetoothRouteController.BluetoothRoutesUpdatedListener
-            mBluetoothRoutesUpdatedListener = routes -> {
-                // Empty on purpose.
-            };
+            mBluetoothRoutesUpdatedListener =
+                    () -> {
+                        // Empty on purpose.
+                    };
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index ec4b8a8..14b121d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -38,7 +38,7 @@
 public class DeviceRouteControllerTest {
 
     private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener =
-            deviceRoute -> {
+            () -> {
                 // Empty on purpose.
             };
 
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 d85768d..ece3dfe 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
@@ -29,6 +29,7 @@
 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.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
@@ -38,6 +39,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
@@ -128,6 +130,14 @@
                 }
             };
 
+    private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+                    return mMediaProjectionMetricsLogger;
+                }
+            };
+
     private Context mContext;
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
@@ -142,6 +152,8 @@
     private PackageManager mPackageManager;
     @Mock
     private IMediaProjectionWatcherCallback mWatcherCallback;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     @Captor
     private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
@@ -299,6 +311,70 @@
     }
 
     @Test
+    public void stop_noActiveProjections_doesNotLog() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+
+        projection.stop();
+
+        verifyZeroInteractions(mMediaProjectionMetricsLogger);
+    }
+
+    @Test
+    public void stop_noSession_logsHostUidAndUnknownTargetUid() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+
+        projection.stop();
+
+        verify(mMediaProjectionMetricsLogger)
+                .logStopped(UID, ContentRecordingSession.TARGET_UID_UNKNOWN);
+    }
+
+    @Test
+    public void stop_displaySession_logsHostUidAndUnknownTargetUidFullScreen() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true)
+                .when(mWindowManagerInternal)
+                .setContentRecordingSession(any(ContentRecordingSession.class));
+        service.setContentRecordingSession(DISPLAY_SESSION);
+
+        projection.stop();
+
+        verify(mMediaProjectionMetricsLogger)
+                .logStopped(UID, ContentRecordingSession.TARGET_UID_FULL_SCREEN);
+    }
+
+    @Test
+    public void stop_taskSession_logsHostUidAndTargetUid() throws Exception {
+        int targetUid = 1234;
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true)
+                .when(mWindowManagerInternal)
+                .setContentRecordingSession(any(ContentRecordingSession.class));
+        ContentRecordingSession taskSession =
+                ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+        service.setContentRecordingSession(taskSession);
+
+        projection.stop();
+
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, targetUid);
+    }
+
+    @Test
     public void testIsValid_multipleStarts_preventionDisabled() throws NameNotFoundException {
         MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
                 mPreventReusedTokenDisabledInjector);
@@ -574,6 +650,51 @@
                 /* isSetSessionSuccessful= */ false, RECORD_CANCEL);
     }
 
+    @Test
+    public void notifyPermissionRequestInitiated_forwardsToLogger() {
+        int hostUid = 123;
+        int sessionCreationSource = 456;
+        mService =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+        mService.notifyPermissionRequestInitiated(hostUid, sessionCreationSource);
+
+        verify(mMediaProjectionMetricsLogger).logInitiated(hostUid, sessionCreationSource);
+    }
+
+    @Test
+    public void notifyPermissionRequestDisplayed_forwardsToLogger() {
+        int hostUid = 123;
+        mService =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+        mService.notifyPermissionRequestDisplayed(hostUid);
+
+        verify(mMediaProjectionMetricsLogger).logPermissionRequestDisplayed(hostUid);
+    }
+
+    @Test
+    public void notifyPermissionRequestCancelled_forwardsToLogger() {
+        int hostUid = 123;
+        mService =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+        mService.notifyPermissionRequestCancelled(hostUid);
+
+        verify(mMediaProjectionMetricsLogger).logProjectionPermissionRequestCancelled(hostUid);
+    }
+
+    @Test
+    public void notifyAppSelectorDisplayed_forwardsToLogger() {
+        int hostUid = 456;
+        mService =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+        mService.notifyAppSelectorDisplayed(hostUid);
+
+        verify(mMediaProjectionMetricsLogger).logAppSelectorDisplayed(hostUid);
+    }
+
     /**
      * Executes and validates scenario where the consent result indicates the projection ends.
      */
@@ -734,6 +855,86 @@
     }
 
     @Test
+    public void setContentRecordingSession_success_logsCaptureInProgress()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        service.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mMediaProjectionMetricsLogger).logInProgress(
+                projection.uid,
+                DISPLAY_SESSION.getTargetUid()
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_taskSession_logsCaptureInProgressWithTargetUid()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true)
+                .when(mWindowManagerInternal)
+                .setContentRecordingSession(any(ContentRecordingSession.class));
+        int targetUid = 123455;
+
+        ContentRecordingSession taskSession =
+                ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+        service.setContentRecordingSession(taskSession);
+
+        verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid);
+    }
+
+    @Test
+    public void setContentRecordingSession_failure_doesNotLogCaptureInProgress() throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        service.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+                anyInt(),
+                anyInt()
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionNull_doesNotLogCaptureInProgress()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        service.setContentRecordingSession(null);
+
+        verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+                anyInt(),
+                anyInt()
+        );
+    }
+
+    @Test
     public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
             throws Exception {
         mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
new file mode 100644
index 0000000..ad1cd6e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.projection;
+
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+
+/**
+ * Tests for the {@link MediaProjectionMetricsLoggerTest} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionMetricsLoggerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionMetricsLoggerTest {
+
+    private static final int TEST_HOST_UID = 123;
+    private static final int TEST_TARGET_UID = 456;
+    private static final int TEST_CREATION_SOURCE = 789;
+
+    @Mock private FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
+    @Mock private MediaProjectionSessionIdGenerator mSessionIdGenerator;
+    @Mock private MediaProjectionTimestampStore mTimestampStore;
+
+    private MediaProjectionMetricsLogger mLogger;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mLogger =
+                new MediaProjectionMetricsLogger(
+                        mFrameworkStatsLogWrapper, mSessionIdGenerator, mTimestampStore);
+    }
+
+    @Test
+    public void logInitiated_logsStateChangedAtomId() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logInitiated_logsStateInitiated() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logInitiated_logsHostUid() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logInitiated_logsSessionCreationSource() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyCreationSourceLogged(TEST_CREATION_SOURCE);
+    }
+
+    @Test
+    public void logInitiated_logsUnknownTargetUid() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyTargetUidLogged(-2);
+    }
+
+    @Test
+    public void logInitiated_noPreviousSession_logsUnknownTimeSinceLastActive() {
+        when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(null);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logInitiated_previousSession_logsTimeSinceLastActiveInSeconds() {
+        Duration timeSinceLastActiveSession = Duration.ofHours(1234);
+        when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(timeSinceLastActiveSession);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyTimeSinceLastActiveSessionLogged((int) timeSinceLastActiveSession.toSeconds());
+    }
+
+    @Test
+    public void logInitiated_logsNewSessionId() {
+        int newSessionId = 123;
+        when(mSessionIdGenerator.createAndGetNewSessionId()).thenReturn(newSessionId);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifySessionIdLogged(newSessionId);
+    }
+
+    @Test
+    public void logInitiated_logsPreviousState() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logStopped_logsStateChangedAtomId() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logStopped_logsCurrentSessionId() {
+        int currentSessionId = 987;
+        when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifySessionIdLogged(currentSessionId);
+    }
+
+    @Test
+    public void logStopped_logsStateStopped() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+    }
+
+    @Test
+    public void logStopped_logsHostUid() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logStopped_logsTargetUid() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyTargetUidLogged(TEST_TARGET_UID);
+    }
+
+    @Test
+    public void logStopped_logsUnknownTimeSinceLastActive() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logStopped_logsUnknownSessionCreationSource() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyCreationSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    @Test
+    public void logStopped_logsPreviousState() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logStopped_capturingWasInProgress_registersActiveSessionEnded() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verify(mTimestampStore).registerActiveSessionEnded();
+    }
+
+    @Test
+    public void logStopped_capturingWasNotInProgress_doesNotRegistersActiveSessionEnded() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verify(mTimestampStore, never()).registerActiveSessionEnded();
+    }
+
+    @Test
+    public void logInProgress_logsStateChangedAtomId() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logInProgress_logsCurrentSessionId() {
+        int currentSessionId = 987;
+        when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifySessionIdLogged(currentSessionId);
+    }
+
+    @Test
+    public void logInProgress_logsStateInProgress() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+    }
+
+    @Test
+    public void logInProgress_logsHostUid() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logInProgress_logsTargetUid() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyTargetUidLogged(TEST_TARGET_UID);
+    }
+
+    @Test
+    public void logInProgress_logsUnknownTimeSinceLastActive() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logInProgress_logsUnknownSessionCreationSource() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyCreationSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    @Test
+    public void logInProgress_logsPreviousState() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+
+        mLogger.logInProgress(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsStateChangedAtomId() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsCurrentSessionId() {
+        int currentSessionId = 765;
+        when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifySessionIdLogged(currentSessionId);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsStateDisplayed() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsHostUid() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsUnknownTargetUid() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyTargetUidLogged(-2);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsUnknownTimeSinceLastActive() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsUnknownSessionCreationSource() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyCreationSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    @Test
+    public void logPermissionRequestDisplayed_logsPreviousState() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsStateChangedAtomId() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsCurrentSessionId() {
+        int currentSessionId = 765;
+        when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifySessionIdLogged(currentSessionId);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsStateDisplayed() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsHostUid() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsUnknownTargetUid() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyTargetUidLogged(-2);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsUnknownTimeSinceLastActive() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsUnknownSessionCreationSource() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyCreationSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    @Test
+    public void logAppSelectorDisplayed_logsPreviousState() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
+
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsStateChangedAtomId() {
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsExistingSessionId() {
+        int existingSessionId = 456;
+        when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(existingSessionId);
+
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifySessionIdLogged(existingSessionId);
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsStateCancelled() {
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED);
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsPreviousState() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsHostUid() {
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsUnknownTargetUid() {
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifyTargetUidLogged(-2);
+    }
+
+    @Test
+    public void logProjectionPermissionRequestCancelled_logsUnknownCreationSource() {
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifyCreationSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    private void verifyStateChangedAtomIdLogged() {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ eq(FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyStateLogged(int state) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        eq(state),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyHostUidLogged(int hostUid) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        eq(hostUid),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyCreationSourceLogged(int creationSource) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        eq(creationSource));
+    }
+
+    private void verifyTargetUidLogged(int targetUid) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        eq(targetUid),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyTimeSinceLastActiveSessionLogged(int timeSinceLastActiveSession) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ eq(timeSinceLastActiveSession),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifySessionIdLogged(int newSessionId) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ eq(newSessionId),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyPreviousStateLogged(int previousState) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        eq(previousState),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
new file mode 100644
index 0000000..07cdf4d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Tests for the {@link MediaProjectionSessionIdGenerator} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionSessionIdGeneratorTest {
+
+    private static final String TEST_PREFS_FILE = "media-projection-session-id-test";
+
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+    private final SharedPreferences mSharedPreferences = createSharePreferences();
+    private final MediaProjectionSessionIdGenerator mGenerator =
+            createGenerator(mSharedPreferences);
+
+    @Before
+    public void setUp() {
+        mSharedPreferences.edit().clear().commit();
+    }
+
+    @After
+    public void tearDown() {
+        mSharedPreferences.edit().clear().commit();
+        mSharedPreferencesFile.delete();
+    }
+
+    @Test
+    public void getCurrentSessionId_byDefault_returns0() {
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+    }
+
+    @Test
+    public void getCurrentSessionId_multipleTimes_returnsSameValue() {
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+    }
+
+    @Test
+    public void createAndGetNewSessionId_returnsIncrementedId() {
+        int previousValue = mGenerator.getCurrentSessionId();
+
+        int newValue = mGenerator.createAndGetNewSessionId();
+
+        assertThat(newValue).isEqualTo(previousValue + 1);
+    }
+
+    @Test
+    public void createAndGetNewSessionId_persistsNewValue() {
+        int newValue = mGenerator.createAndGetNewSessionId();
+
+        MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences());
+
+        assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue);
+    }
+
+    private SharedPreferences createSharePreferences() {
+        return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+    }
+
+    private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) {
+        return new MediaProjectionSessionIdGenerator(sharedPreferences);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java
new file mode 100644
index 0000000..7723541
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java
@@ -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.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.InstantSource;
+
+/**
+ * Tests for the {@link MediaProjectionTimestampStore} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionTimestampStoreTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionTimestampStoreTest {
+
+    private static final String TEST_PREFS_FILE = "media-projection-timestamp-test";
+
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+    private final SharedPreferences mSharedPreferences = createSharePreferences();
+
+    private Instant mCurrentInstant = Instant.ofEpochMilli(0);
+
+    private final InstantSource mInstantSource = () -> mCurrentInstant;
+    private final MediaProjectionTimestampStore mStore =
+            new MediaProjectionTimestampStore(mSharedPreferences, mInstantSource);
+
+    @Before
+    public void setUp() {
+        mSharedPreferences.edit().clear().commit();
+    }
+
+    @After
+    public void tearDown() {
+        mSharedPreferences.edit().clear().commit();
+        mSharedPreferencesFile.delete();
+    }
+
+    @Test
+    public void timeSinceLastActiveSession_byDefault_returnsNull() {
+        assertThat(mStore.timeSinceLastActiveSession()).isNull();
+    }
+
+    @Test
+    public void timeSinceLastActiveSession_returnsBasedOnLastActiveSessionEnded() {
+        mCurrentInstant = Instant.ofEpochMilli(0);
+        mStore.registerActiveSessionEnded();
+
+        mCurrentInstant = mCurrentInstant.plusSeconds(60);
+
+        assertThat(mStore.timeSinceLastActiveSession()).isEqualTo(Duration.ofSeconds(60));
+    }
+
+    @Test
+    public void timeSinceLastActiveSession_valueIsPersisted() {
+        mCurrentInstant = Instant.ofEpochMilli(0);
+        mStore.registerActiveSessionEnded();
+
+        MediaProjectionTimestampStore newStoreInstance =
+                new MediaProjectionTimestampStore(createSharePreferences(), mInstantSource);
+        mCurrentInstant = mCurrentInstant.plusSeconds(123);
+
+        assertThat(newStoreInstance.timeSinceLastActiveSession())
+                .isEqualTo(Duration.ofSeconds(123));
+    }
+
+    private SharedPreferences createSharePreferences() {
+        return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
new file mode 100644
index 0000000..949f8e7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.text.TextUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.server.connectivity.Vpn;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LockdownVpnTrackerTest {
+    private static final NetworkCapabilities TEST_CELL_NC = new NetworkCapabilities.Builder()
+            .addTransportType(TRANSPORT_CELLULAR)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+            .build();
+    private static final LinkProperties TEST_CELL_LP = new LinkProperties();
+
+    static {
+        TEST_CELL_LP.setInterfaceName("rmnet0");
+        TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25"));
+    }
+
+    // Use a context wrapper instead of a mock since LockdownVpnTracker builds notifications which
+    // is tedious and currently unnecessary to mock.
+    private final Context mContext = new ContextWrapper(InstrumentationRegistry.getContext()) {
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
+            if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
+
+            return super.getSystemService(name);
+        }
+    };
+    @Mock private ConnectivityManager mCm;
+    @Mock private Vpn mVpn;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private NetworkInfo mVpnNetworkInfo;
+    @Mock private VpnConfig mVpnConfig;
+    @Mock private Network mNetwork;
+    @Mock private Network mNetwork2;
+    @Mock private Network mVpnNetwork;
+
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private VpnProfile mProfile;
+
+    private VpnProfile createTestVpnProfile() {
+        final String profileName = "testVpnProfile";
+        final VpnProfile profile = new VpnProfile(profileName);
+        profile.name = "My VPN";
+        profile.server = "192.0.2.1";
+        profile.dnsServers = "8.8.8.8";
+        profile.ipsecIdentifier = "My ipsecIdentifier";
+        profile.ipsecSecret = "My PSK";
+        profile.type = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
+
+        return profile;
+    }
+
+    private NetworkCallback getDefaultNetworkCallback() {
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mCm).registerSystemDefaultNetworkCallback(callbackCaptor.capture(), eq(mHandler));
+        return callbackCaptor.getValue();
+    }
+
+    private NetworkCallback getVpnNetworkCallback() {
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mCm).registerNetworkCallback(any(), callbackCaptor.capture(), eq(mHandler));
+        return callbackCaptor.getValue();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread("LockdownVpnTrackerTest");
+        mHandlerThread.start();
+        mHandler = mHandlerThread.getThreadHandler();
+
+        doReturn(mVpnNetworkInfo).when(mVpn).getNetworkInfo();
+        doReturn(false).when(mVpnNetworkInfo).isConnectedOrConnecting();
+        doReturn(mVpnConfig).when(mVpn).getLegacyVpnConfig();
+        // mVpnConfig is a mock but the production code will try to add addresses in this array
+        // assuming it's non-null, so it needs to be initialized.
+        mVpnConfig.addresses = new ArrayList<>();
+
+        mProfile = createTestVpnProfile();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread.join();
+        }
+    }
+
+    private LockdownVpnTracker initAndVerifyLockdownVpnTracker() {
+        final LockdownVpnTracker lockdownVpnTracker =
+                new LockdownVpnTracker(mContext, mHandler, mVpn, mProfile);
+        lockdownVpnTracker.init();
+        verify(mVpn).setEnableTeardown(false);
+        verify(mVpn).setLockdown(true);
+        verify(mCm).setLegacyLockdownVpnEnabled(true);
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+
+        return lockdownVpnTracker;
+    }
+
+    private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network,
+            NetworkCapabilities nc, LinkProperties lp, boolean blocked) {
+        callback.onAvailable(network);
+        callback.onCapabilitiesChanged(network, nc);
+        callback.onLinkPropertiesChanged(network, lp);
+        callback.onBlockedStatusChanged(network, blocked);
+    }
+
+    private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network) {
+        callCallbacksForNetworkConnect(
+                callback, network, TEST_CELL_NC, TEST_CELL_LP, true /* blocked */);
+    }
+
+    private boolean isExpectedNotification(Notification notification, int titleRes, int iconRes) {
+        if (!NOTIFICATION_CHANNEL_VPN.equals(notification.getChannelId())) {
+            return false;
+        }
+        final CharSequence expectedTitle = mContext.getString(titleRes);
+        final CharSequence actualTitle = notification.extras.getCharSequence(
+                Notification.EXTRA_TITLE);
+        if (!TextUtils.equals(expectedTitle, actualTitle)) {
+            return false;
+        }
+        return notification.getSmallIcon().getResId() == iconRes;
+    }
+
+    @Test
+    public void testShutdown() {
+        final LockdownVpnTracker lockdownVpnTracker = initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        final NetworkCallback vpnCallback = getVpnNetworkCallback();
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        lockdownVpnTracker.shutdown();
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mVpn).setLockdown(false);
+        verify(mCm).setLegacyLockdownVpnEnabled(false);
+        verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+        verify(mVpn).setEnableTeardown(true);
+        verify(mCm).unregisterNetworkCallback(defaultCallback);
+        verify(mCm).unregisterNetworkCallback(vpnCallback);
+    }
+
+    @Test
+    public void testDefaultNetworkConnected() {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        // mNetwork connected and available.
+        callCallbacksForNetworkConnect(defaultCallback, mNetwork);
+
+        // Vpn is starting
+        verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP);
+        verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
+                argThat(notification -> isExpectedNotification(notification,
+                        R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
+    }
+
+    private void doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp) {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        callCallbacksForNetworkConnect(
+                defaultCallback, mNetwork, TEST_CELL_NC, startingLp, true /* blocked */);
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        // LockdownVpnTracker#handleStateChangedLocked() is not called on the same network even if
+        // the LinkProperties change.
+        defaultCallback.onLinkPropertiesChanged(mNetwork, newLp);
+
+        // Ideally the VPN should start if it hasn't already, but it doesn't because nothing calls
+        // LockdownVpnTracker#handleStateChangedLocked. This is a bug.
+        // TODO: consider fixing this.
+        verify(mVpn, never()).stopVpnRunnerPrivileged();
+        verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any());
+        verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+    }
+
+    @Test
+    public void testDefaultLPChanged_V4AddLinkAddressV4() {
+        final LinkProperties lp = new LinkProperties(TEST_CELL_LP);
+        lp.setInterfaceName("rmnet0");
+        lp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
+        doTestDefaultLpChanged(TEST_CELL_LP, lp);
+    }
+
+    @Test
+    public void testDefaultLPChanged_V4AddLinkAddressV6() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("rmnet0");
+        lp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
+        final LinkProperties newLp = new LinkProperties(lp);
+        newLp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        doTestDefaultLpChanged(lp, newLp);
+    }
+
+    @Test
+    public void testDefaultLPChanged_V6AddLinkAddressV4() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("rmnet0");
+        lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        final LinkProperties newLp = new LinkProperties(lp);
+        newLp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
+        doTestDefaultLpChanged(lp, newLp);
+    }
+
+    @Test
+    public void testDefaultLPChanged_AddLinkAddressV4() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("rmnet0");
+        doTestDefaultLpChanged(lp, TEST_CELL_LP);
+    }
+
+    @Test
+    public void testDefaultNetworkChanged() {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        final NetworkCallback vpnCallback = getVpnNetworkCallback();
+        callCallbacksForNetworkConnect(defaultCallback, mNetwork);
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        // New network and LinkProperties received
+        final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .build();
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName("wlan0");
+        callCallbacksForNetworkConnect(
+                defaultCallback, mNetwork2, wifiNc, wifiLp, true /* blocked */);
+
+        // Vpn is restarted.
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp);
+        verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+        verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
+                argThat(notification -> isExpectedNotification(notification,
+                        R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
+
+        // Vpn is Connected
+        doReturn(true).when(mVpnNetworkInfo).isConnectedOrConnecting();
+        doReturn(true).when(mVpnNetworkInfo).isConnected();
+        vpnCallback.onAvailable(mVpnNetwork);
+        verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
+                argThat(notification -> isExpectedNotification(notification,
+                        R.string.vpn_lockdown_connected, R.drawable.vpn_connected)));
+
+    }
+
+    @Test
+    public void testSystemDefaultLost() {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        // mNetwork connected
+        callCallbacksForNetworkConnect(defaultCallback, mNetwork);
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        defaultCallback.onLost(mNetwork);
+
+        // Vpn is stopped
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+    }
+}
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 fc27edc..a4d50f0 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -94,27 +94,31 @@
     public void testBugreportFileManagerFileExists() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
-                callingInfo, mBugreportFile);
+                callingInfo, mBugreportFile, /* keepOnRetrieval= */ false);
 
         assertThrows(IllegalArgumentException.class, () ->
                 mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                        callingInfo, "unknown-file.zip"));
+                        mContext, callingInfo, Process.myUserHandle().getIdentifier(),
+                        "unknown-file.zip"));
 
         // No exception should be thrown.
-        mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+        mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+                mContext, callingInfo, mContext.getUserId(), mBugreportFile);
     }
 
     @Test
     public void testBugreportFileManagerMultipleFiles() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
-                callingInfo, mBugreportFile);
+                callingInfo, mBugreportFile, /* keepOnRetrieval= */ false);
         mBugreportFileManager.addBugreportFileForCaller(
-                callingInfo, mBugreportFile2);
+                callingInfo, mBugreportFile2, /* keepOnRetrieval= */ false);
 
         // No exception should be thrown.
-        mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
-        mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2);
+        mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+                mContext, callingInfo, mContext.getUserId(), mBugreportFile);
+        mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+                mContext, callingInfo, mContext.getUserId(), mBugreportFile2);
     }
 
     @Test
@@ -122,7 +126,8 @@
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         assertThrows(IllegalArgumentException.class,
                 () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                        callingInfo, "test-file.zip"));
+                        mContext, callingInfo, Process.myUserHandle().getIdentifier(),
+                        "test-file.zip"));
     }
 
     @Test
@@ -130,7 +135,8 @@
         CountDownLatch latch = new CountDownLatch(1);
         Listener listener = new Listener(latch);
         mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
-                new FileDescriptor(), mBugreportFile, listener);
+                mContext.getUserId(), new FileDescriptor(), mBugreportFile,
+                /* keepOnRetrieval= */ false, listener);
         assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
         assertThat(listener.getErrorCode()).isEqualTo(
                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/tests/servicestests/src/com/android/server/pdb/OWNERS b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
new file mode 100644
index 0000000..6dfb888
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pdb/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 42be3d3..5d6f36c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -19,7 +19,6 @@
 import android.content.pm.SigningDetails;
 import android.util.SparseArray;
 
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 
@@ -159,17 +158,19 @@
 
     public PackageSetting build() {
         final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
-                new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
-                mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
-                mPrivateFlags, mSharedUserId, null /* usesSdkLibraries */,
-                null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */,
-                null  /* usesStaticLibrariesVersions */, mMimeGroups, mDomainSetId);
-        packageSetting.setSignatures(mSigningDetails != null
-                ? new PackageSignatures(mSigningDetails)
-                : new PackageSignatures());
-        packageSetting.setPkg((ParsedPackage) mPkg);
-        packageSetting.setAppId(mAppId);
-        packageSetting.setVolumeUuid(this.mVolumeUuid);
+                new File(mCodePath), mPkgFlags, mPrivateFlags, mDomainSetId)
+                .setLegacyNativeLibraryPath(mLegacyNativeLibraryPathString)
+                .setPrimaryCpuAbi(mPrimaryCpuAbiString)
+                .setSecondaryCpuAbi(mSecondaryCpuAbiString)
+                .setCpuAbiOverride(mCpuAbiOverrideString)
+                .setLongVersionCode(mPVersionCode)
+                .setSharedUserAppId(mSharedUserId)
+                .setMimeGroups(mMimeGroups)
+                .setSignatures(mSigningDetails != null
+                        ? new PackageSignatures(mSigningDetails) : new PackageSignatures())
+                .setPkg(mPkg)
+                .setAppId(mAppId)
+                .setVolumeUuid(this.mVolumeUuid);
         if (mInstallSource != null) {
             packageSetting.setInstallSource(mInstallSource);
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 9f75cf8..253592c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -43,6 +43,7 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.multiuser.Flags;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.UserHandle;
@@ -124,18 +125,34 @@
 
         mUserManagerService.putUserInfo(data.info);
 
-        // Set a global and user restriction so they get written out to the user file.
+        //Local restrictions are written to the user specific files and global restrictions
+        // are written to the SYSTEM user file.
         setUserRestrictions(data.info.id, globalRestriction, localRestriction, true);
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         DataOutputStream out = new DataOutputStream(baos);
         mUserManagerService.writeUserLP(data, out);
-        byte[] bytes = baos.toByteArray();
+        byte[] secondaryUserBytes = baos.toByteArray();
+        baos.reset();
+
+        byte[] systemUserBytes = new byte[0];
+        if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+            UserData systemUserData = new UserData();
+            systemUserData.info = mUserManagerService.getUserInfo(UserHandle.USER_SYSTEM);
+            mUserManagerService.writeUserLP(systemUserData, baos);
+            systemUserBytes = baos.toByteArray();
+        }
 
         // Clear the restrictions to see if they are properly read in from the user file.
         setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
 
-        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes));
+        //read the secondary and SYSTEM user file to fetch local/global device policy restrictions.
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes));
+        if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+            mUserManagerService.readUserLP(UserHandle.USER_SYSTEM,
+                    new ByteArrayInputStream(systemUserBytes));
+        }
+
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction));
     }
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 a54bc91..c684a7b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -60,6 +60,7 @@
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
                 .setShowInSettings(45)
+                .setHideInSettingsInQuietMode(false)
                 .setInheritDevicePolicy(67)
                 .setUseParentsContacts(false)
                 .setCrossProfileIntentFilterAccessControl(10)
@@ -72,6 +73,7 @@
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
         actualProps.setShowInSettings(32);
+        actualProps.setHideInSettingsInQuietMode(true);
         actualProps.setInheritDevicePolicy(51);
         actualProps.setUseParentsContacts(true);
         actualProps.setCrossProfileIntentFilterAccessControl(20);
@@ -228,6 +230,8 @@
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
         assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
+        assertThat(expected.getHideInSettingsInQuietMode())
+                .isEqualTo(actual.getHideInSettingsInQuietMode());
         assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
         assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
         assertThat(expected.getCrossProfileIntentFilterAccessControl())
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 e3579b4..20270a8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -90,6 +90,7 @@
                 .setMediaSharedWithParent(true)
                 .setCredentialShareableWithParent(false)
                 .setShowInSettings(900)
+                .setHideInSettingsInQuietMode(true)
                 .setInheritDevicePolicy(340)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(true);
@@ -160,6 +161,7 @@
         assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent());
         assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
         assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
+        assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(340, type.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent());
@@ -217,6 +219,7 @@
         assertFalse(props.isCredentialShareableWithParent());
         assertFalse(props.getDeleteAppWithParent());
         assertFalse(props.getAlwaysVisible());
+        assertFalse(props.getHideInSettingsInQuietMode());
 
         assertFalse(type.hasBadge());
     }
@@ -304,6 +307,7 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setShowInSettings(20)
+                .setHideInSettingsInQuietMode(false)
                 .setInheritDevicePolicy(21)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(false);
@@ -344,6 +348,7 @@
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .isCredentialShareableWithParent());
         assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
+        assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
@@ -390,6 +395,7 @@
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .isCredentialShareableWithParent());
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
+        assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(450, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
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 5dfce06..89c6a220 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -974,7 +974,6 @@
         assertThrows(SecurityException.class, userProps::getAlwaysVisible);
     }
 
-
     // Make sure only max managed profiles can be created
     @MediumTest
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 9fca513..d09aa89 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -25,8 +25,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -46,6 +44,7 @@
 import android.os.IHintSession;
 import android.os.PerformanceHintManager;
 import android.os.Process;
+import android.util.Log;
 
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -58,7 +57,14 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
 
 /**
  * Tests for {@link com.android.server.power.hint.HintManagerService}.
@@ -67,8 +73,11 @@
  *  atest FrameworksServicesTests:HintManagerServiceTest
  */
 public class HintManagerServiceTest {
+    private static final String TAG = "HintManagerServiceTest";
+
     private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L;
     private static final long DEFAULT_TARGET_DURATION = 16666666L;
+    private static final long CONCURRENCY_TEST_DURATION_SEC = 10;
     private static final int UID = Process.myUid();
     private static final int TID = Process.myPid();
     private static final int TGID = Process.getThreadGroupLeader(TID);
@@ -103,6 +112,55 @@
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
     }
 
+    static class NativeWrapperFake extends NativeWrapper {
+        @Override
+        public void halInit() {
+        }
+
+        @Override
+        public long halGetHintSessionPreferredRate() {
+            return 1;
+        }
+
+        @Override
+        public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
+            return 1;
+        }
+
+        @Override
+        public void halPauseHintSession(long halPtr) {
+        }
+
+        @Override
+        public void halResumeHintSession(long halPtr) {
+        }
+
+        @Override
+        public void halCloseHintSession(long halPtr) {
+        }
+
+        @Override
+        public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
+        }
+
+        @Override
+        public void halReportActualWorkDuration(
+                long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
+        }
+
+        @Override
+        public void halSendHint(long halPtr, int hint) {
+        }
+
+        @Override
+        public void halSetThreads(long halPtr, int[] tids) {
+        }
+
+        @Override
+        public void halSetMode(long halPtr, int mode, boolean enabled) {
+        }
+    }
+
     private HintManagerService createService() {
         mService = new HintManagerService(mContext, new Injector() {
             NativeWrapper createNativeWrapper() {
@@ -112,6 +170,15 @@
         return mService;
     }
 
+    private HintManagerService createServiceWithFakeWrapper() {
+        mService = new HintManagerService(mContext, new Injector() {
+            NativeWrapper createNativeWrapper() {
+                return new NativeWrapperFake();
+            }
+        });
+        return mService;
+    }
+
     @Test
     public void testInitializeService() {
         HintManagerService service = createService();
@@ -166,8 +233,7 @@
             latch.countDown();
         });
         latch.await();
-
-        assumeFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong());
 
         // Set session to foreground and calling updateHintAllowed() would invoke resume();
@@ -181,7 +247,7 @@
         });
         latch2.await();
 
-        assumeTrue(a.updateHintAllowed());
+        assertTrue(service.mUidObserver.isUidForeground(a.mUid));
         verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
     }
 
@@ -254,7 +320,7 @@
         });
         latch.await();
 
-        assumeFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
         verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
     }
@@ -280,7 +346,7 @@
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         FgThread.getHandler().runWithScissors(() -> { }, 500);
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
         verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
     }
@@ -303,7 +369,7 @@
         });
         latch.await();
 
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
     }
 
     @Test
@@ -316,7 +382,7 @@
 
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
-        assertTrue(a.updateHintAllowed());
+        assertTrue(service.mUidObserver.isUidForeground(a.mUid));
     }
 
     @Test
@@ -342,7 +408,7 @@
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         FgThread.getHandler().runWithScissors(() -> { }, 500);
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.setThreads(SESSION_TIDS_A);
         verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
     }
@@ -372,9 +438,159 @@
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         FgThread.getHandler().runWithScissors(() -> { }, 500);
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.setMode(0, true);
         verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
     }
 
+    // This test checks that concurrent operations from different threads on IHintService,
+    // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also
+    // check the output of threads' reportActualDuration performance to detect lock starvation
+    // but the result is not stable, so it's better checked manually.
+    @Test
+    public void testConcurrency() throws Exception {
+        HintManagerService service = createServiceWithFakeWrapper();
+        // initialize session threads to run in parallel
+        final int sessionCount = 10;
+        // the signal that the main thread will send to session threads to check for run or exit
+        AtomicReference<Boolean> shouldRun = new AtomicReference<>(true);
+        // the signal for main test thread to wait for session threads and notifier thread to
+        // finish and exit
+        CountDownLatch latch = new CountDownLatch(sessionCount + 1);
+        // list of exceptions with one per session thread or notifier thread
+        List<AtomicReference<Exception>> execs = new ArrayList<>(sessionCount + 1);
+        List<Thread> threads = new ArrayList<>(sessionCount + 1);
+        for (int i = 0; i < sessionCount; i++) {
+            final AtomicReference<Exception> exec = new AtomicReference<>();
+            execs.add(exec);
+            int j = i;
+            Thread app = new Thread(() -> {
+                try {
+                    while (shouldRun.get()) {
+                        runAppHintSession(service, j, shouldRun);
+                    }
+                } catch (Exception e) {
+                    exec.set(e);
+                } finally {
+                    latch.countDown();
+                }
+            });
+            threads.add(app);
+        }
+
+        // initialize a UID state notifier thread to run in parallel
+        final AtomicReference<Exception> notifierExec = new AtomicReference<>();
+        execs.add(notifierExec);
+        Thread notifier = new Thread(() -> {
+            try {
+                long min = Long.MAX_VALUE;
+                long max = Long.MIN_VALUE;
+                long sum = 0;
+                int count = 0;
+                while (shouldRun.get()) {
+                    long start = System.nanoTime();
+                    service.mUidObserver.onUidStateChanged(UID,
+                            ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+                    long elapsed = System.nanoTime() - start;
+                    sum += elapsed;
+                    count++;
+                    min = Math.min(min, elapsed);
+                    max = Math.max(max, elapsed);
+                    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
+                    service.mUidObserver.onUidStateChanged(UID,
+                            ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+                    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
+                }
+                Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count);
+                service.mUidObserver.onUidGone(UID, true);
+            } catch (Exception e) {
+                notifierExec.set(e);
+            } finally {
+                latch.countDown();
+            }
+        });
+        threads.add(notifier);
+
+        // start all the threads
+        for (Thread thread : threads) {
+            thread.start();
+        }
+        // keep the test running for a few seconds
+        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(CONCURRENCY_TEST_DURATION_SEC));
+        // send signal to stop all threads
+        shouldRun.set(false);
+        // wait for all threads to exit
+        latch.await();
+        // check if any thread throws exception
+        for (AtomicReference<Exception> exec : execs) {
+            if (exec.get() != null) {
+                throw exec.get();
+            }
+        }
+    }
+
+    private void runAppHintSession(HintManagerService service, int logId,
+            AtomicReference<Boolean> shouldRun) throws Exception {
+        IBinder token = new Binder();
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+        // we will start some threads and get their valid TIDs to update
+        int threadCount = 3;
+        // the list of TIDs
+        int[] tids = new int[threadCount];
+        // atomic index for each thread to set its TID in the list
+        AtomicInteger k = new AtomicInteger(0);
+        // signal for the session main thread to wait for child threads to finish updating TIDs
+        CountDownLatch latch = new CountDownLatch(threadCount);
+        // signal for the session main thread to notify child threads to exit
+        CountDownLatch stopLatch = new CountDownLatch(1);
+        for (int j = 0; j < threadCount; j++) {
+            Thread thread = new Thread(() -> {
+                try {
+                    tids[k.getAndIncrement()] = android.os.Process.myTid();
+                    latch.countDown();
+                    stopLatch.await();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            thread.start();
+        }
+        latch.await();
+        a.setThreads(tids);
+        // we don't need the threads to exist after update
+        stopLatch.countDown();
+        a.updateTargetWorkDuration(5);
+        // measure the time it takes in HintManagerService to run reportActualDuration
+        long min = Long.MAX_VALUE;
+        long max = Long.MIN_VALUE;
+        long sum = 0;
+        int count = 0;
+        List<Long> values = new ArrayList<>();
+        long testStart = System.nanoTime();
+        // run report actual for 4-second per cycle
+        while (shouldRun.get() && System.nanoTime() - testStart < TimeUnit.SECONDS.toNanos(
+                Math.min(4, CONCURRENCY_TEST_DURATION_SEC))) {
+            long start = System.nanoTime();
+            a.reportActualWorkDuration(new long[]{5}, new long[]{start});
+            long elapsed = System.nanoTime() - start;
+            values.add(elapsed);
+            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(5));
+            sum += elapsed;
+            count++;
+            min = Math.min(min, elapsed);
+            max = Math.max(max, elapsed);
+        }
+        Collections.sort(values);
+        if (!values.isEmpty()) {
+            Log.d(TAG, "app thread " + logId + " min " + min + " max " + max
+                    + " avg " + sum / count + " count " + count
+                    + " 80th " + values.get((int) (values.size() * 0.8))
+                    + " 90th " + values.get((int) (values.size() * 0.9))
+                    + " 95th " + values.get((int) (values.size() * 0.95)));
+        } else {
+            Log.w(TAG, "No reportActualWorkDuration executed");
+        }
+        a.close();
+    }
 }
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index d1f4961..8891413 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -43,6 +43,8 @@
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
+        "flag-junit",
+        "notification_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 9742384..42ad73a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,6 +32,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -147,6 +148,7 @@
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
     private static final int MAX_VIBRATION_DELAY = 1000;
+    private static final float DEFAULT_VOLUME = 1.0f;
 
     @Before
     public void setUp() throws Exception {
@@ -397,19 +399,22 @@
     //
 
     private void verifyNeverBeep() throws RemoteException {
-        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat());
     }
 
     private void verifyBeepUnlooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeepLooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeep(int times)  throws RemoteException  {
-        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyNeverStopAudio() throws RemoteException {
@@ -905,7 +910,7 @@
         verifyDelayedVibrate(
                 mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false));
         verify(mRingtonePlayer, never()).playAsync
-                (anyObject(), anyObject(), anyBoolean(), anyObject());
+                (anyObject(), anyObject(), anyBoolean(), anyObject(), anyFloat());
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
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 81867df..cf8548c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -57,6 +57,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
@@ -66,9 +67,11 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -77,7 +80,9 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
+
 import androidx.test.runner.AndroidJUnit4;
+
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
@@ -87,8 +92,12 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.pm.PackageManagerService;
+
+import java.util.List;
 import java.util.Objects;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -102,6 +111,8 @@
 @RunWith(AndroidJUnit4.class)
 @SuppressLint("GuardedBy")
 public class NotificationAttentionHelperTest extends UiServiceTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock AudioManager mAudioManager;
     @Mock Vibrator mVibrator;
@@ -115,6 +126,8 @@
     IAccessibilityManager mAccessibilityService;
     @Mock
     KeyguardManager mKeyguardManager;
+    @Mock
+    private UserManager mUserManager;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
         1 << 30);
@@ -134,6 +147,8 @@
     private AccessibilityManager mAccessibilityManager;
     private static final NotificationAttentionHelper.Signals DEFAULT_SIGNALS =
         new NotificationAttentionHelper.Signals(false, 0);
+    private static final NotificationAttentionHelper.Signals WORK_PROFILE_SIGNALS =
+            new NotificationAttentionHelper.Signals(true, 0);
 
     private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
     private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
@@ -151,6 +166,7 @@
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
     private static final int MAX_VIBRATION_DELAY = 1000;
+    private static final float DEFAULT_VOLUME = 1.0f;
 
     @Before
     public void setUp() throws Exception {
@@ -177,7 +193,9 @@
         assertTrue(mAccessibilityManager.isEnabled());
 
         // TODO (b/291907312): remove feature flag
-        mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
+        // Disable feature flags by default. Tests should enable as needed.
+        mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
 
         mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
             mNotificationInstanceIdSequence));
@@ -189,9 +207,9 @@
 
     private void initAttentionHelper(TestableFlagResolver flagResolver) {
         mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
-            mAccessibilityManager, getContext().getPackageManager(), mUsageStats,
+            mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
             mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
-        mAttentionHelper.setVibratorHelper(new VibratorHelper(getContext()));
+        mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
         mAttentionHelper.setAudioManager(mAudioManager);
         mAttentionHelper.setSystemReady(true);
         mAttentionHelper.setLights(mLight);
@@ -282,6 +300,11 @@
             true /* noisy */, true /* buzzy*/, false /* lights */);
     }
 
+    private NotificationRecord getBuzzyBeepyNotification(UserHandle userHandle) {
+        return getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, true /* buzzy*/, false /* lights */, userHandle);
+    }
+
     private NotificationRecord getLightsNotification() {
         return getNotificationRecord(mId, false /* insistent */, false /* once */,
             false /* noisy */, false /* buzzy*/, true /* lights */);
@@ -312,7 +335,13 @@
     private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
         boolean noisy, boolean buzzy, boolean lights) {
         return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
-            lights, null, Notification.GROUP_ALERT_ALL, false);
+            lights, null, Notification.GROUP_ALERT_ALL, false, mUser);
+    }
+
+    private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+            boolean noisy, boolean buzzy, boolean lights, UserHandle userHandle) {
+        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
+                lights, null, Notification.GROUP_ALERT_ALL, false, userHandle);
     }
 
     private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent,
@@ -320,25 +349,25 @@
         boolean noisy, boolean buzzy, boolean lights) {
         return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true,
             true,
-            null, Notification.GROUP_ALERT_ALL, true);
+            null, Notification.GROUP_ALERT_ALL, true, mUser);
     }
 
     private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) {
         return getNotificationRecord(mId, false, false, true, false, false, true, true, true,
-            groupKey, groupAlertBehavior, false);
+            groupKey, groupAlertBehavior, false, mUser);
     }
 
     private NotificationRecord getLightsNotificationRecord(String groupKey,
         int groupAlertBehavior) {
         return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true,
-            true, true, groupKey, groupAlertBehavior, false);
+            true, true, groupKey, groupAlertBehavior, false, mUser);
     }
 
     private NotificationRecord getNotificationRecord(int id,
         boolean insistent, boolean once,
         boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
         boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
-        boolean isLeanback) {
+        boolean isLeanback, UserHandle userHandle) {
 
         final Builder builder = new Builder(getContext())
             .setContentTitle("foo")
@@ -399,7 +428,7 @@
             .thenReturn(isLeanback);
 
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
-            mPid, n, mUser, null, System.currentTimeMillis());
+            mPid, n, userHandle, null, System.currentTimeMillis());
         NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
         mService.addNotification(r);
         return r;
@@ -410,19 +439,26 @@
     //
 
     private void verifyNeverBeep() throws RemoteException {
-        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat());
     }
 
     private void verifyBeepUnlooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeepLooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeep(int times)  throws RemoteException  {
-        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(),
+                eq(DEFAULT_VOLUME));
+    }
+
+    private void verifyBeepVolume(float volume)  throws RemoteException  {
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), anyBoolean(), any(), eq(volume));
     }
 
     private void verifyNeverStopAudio() throws RemoteException {
@@ -921,7 +957,7 @@
             mAttentionHelper.getVibratorHelper().createFallbackVibration(
                 /* insistent= */ false));
         verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(),
-            anyObject());
+            anyObject(), anyFloat());
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
@@ -1948,6 +1984,259 @@
         assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
+    // TODO b/270456865: Only one of the two strategies will be released.
+    //  The other one need to be removed
+    @Test
+    public void testBeepVolume_politeNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 50% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    // TODO b/270456865: Only one of the two strategies will be released.
+    //  The other one need to be removed
+    @Test
+    public void testBeepVolume_politeNotif_Strategy2() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 0% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        // 2nd update should beep at 50% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testVibrationIntensity_politeNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBuzzyBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
+        Mockito.reset(vibratorHelper);
+
+        // update should buzz at 50% intensity
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
+        Mockito.reset(vibratorHelper);
+
+        // 2nd update should buzz at 0% intensity
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+    }
+
+    @Test
+    public void testVibrationIntensity_politeNotif_Strategy2() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBuzzyBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
+        Mockito.reset(vibratorHelper);
+
+        // update should buzz at 0% intensity
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+        Mockito.reset(vibratorHelper);
+
+        // 2nd update should buzz at 50% intensity
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
+    }
+
+    @Test
+    public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+
+        // When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, 1);
+
+        initAttentionHelper(flagResolver);
+        // And screen is unlocked
+        mAttentionHelper.setUserPresent(true);
+
+        NotificationRecord r = getBuzzyBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        // The notification attention should only buzz
+        verifyNeverBeep();
+        verifyVibrate();
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_disabled() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+
+        // When NOTIFICATION_COOLDOWN_ENABLED setting is disabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0);
+
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 100% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_workProfile() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+
+        final int workProfileUserId = mUser.getIdentifier() + 1;
+
+        // Enable notifications cooldown for work profile
+        Settings.System.putIntForUser(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 1, workProfileUserId);
+
+        when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn(
+                List.of(new UserInfo(workProfileUserId, "work_profile", null,
+                        UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE,
+                        UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE))));
+
+        initAttentionHelper(flagResolver);
+
+        final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId));
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 50% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+
+        final int workProfileUserId = mUser.getIdentifier() + 1;
+
+        // Disable notifications cooldown for work profile
+        Settings.System.putIntForUser(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0, workProfileUserId);
+
+        when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn(
+                List.of(new UserInfo(workProfileUserId, "work_profile", null,
+                        UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE,
+                        UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE))));
+
+        initAttentionHelper(flagResolver);
+
+        final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId));
+
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 100% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
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 c05f814..53ca704 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -23,9 +23,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -49,12 +47,10 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
-import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
@@ -158,81 +154,6 @@
         }
     }
 
-    // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking.
-    @Test
-    public void testRankingUpdate_parcel() {
-        NotificationRankingUpdate nru = generateUpdate();
-        Parcel parcel = Parcel.obtain();
-        nru.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
-        assertEquals(nru, nru1);
-    }
-
-    // Tests parceling of RankingMap and RankingMap.equals
-    @Test
-    public void testRankingMap_parcel() {
-        RankingMap rmap = generateUpdate().getRankingMap();
-        Parcel parcel = Parcel.obtain();
-        rmap.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        RankingMap rmap1 = RankingMap.CREATOR.createFromParcel(parcel);
-
-        detailedAssertEquals(rmap, rmap1);
-        assertEquals(rmap, rmap1);
-    }
-
-    // Tests parceling of Ranking and Ranking.equals
-    @Test
-    public void testRanking_parcel() {
-        Ranking ranking = generateUpdate().getRankingMap().getRawRankingObject(mKeys[0]);
-        Parcel parcel = Parcel.obtain();
-        ranking.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Ranking ranking1 = new Ranking(parcel);
-        detailedAssertEquals("rankings differ: ", ranking, ranking1);
-        assertEquals(ranking, ranking1);
-    }
-
-    // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking.
-    @Test
-    public void testRankingUpdate_equals() {
-        NotificationRankingUpdate nru = generateUpdate();
-        NotificationRankingUpdate nru2 = generateUpdate();
-        detailedAssertEquals(nru, nru2);
-        assertEquals(nru, nru2);
-        Ranking tweak = nru2.getRankingMap().getRawRankingObject(mKeys[0]);
-        tweak.populate(
-                tweak.getKey(),
-                tweak.getRank(),
-                !tweak.matchesInterruptionFilter(), // note the inversion here!
-                tweak.getLockscreenVisibilityOverride(),
-                tweak.getSuppressedVisualEffects(),
-                tweak.getImportance(),
-                tweak.getImportanceExplanation(),
-                tweak.getOverrideGroupKey(),
-                tweak.getChannel(),
-                (ArrayList) tweak.getAdditionalPeople(),
-                (ArrayList) tweak.getSnoozeCriteria(),
-                tweak.canShowBadge(),
-                tweak.getUserSentiment(),
-                tweak.isSuspended(),
-                tweak.getLastAudiblyAlertedMillis(),
-                tweak.isNoisy(),
-                (ArrayList) tweak.getSmartActions(),
-                (ArrayList) tweak.getSmartReplies(),
-                tweak.canBubble(),
-                tweak.isTextChanged(),
-                tweak.isConversation(),
-                tweak.getConversationShortcutInfo(),
-                tweak.getRankingAdjustment(),
-                tweak.isBubble(),
-                tweak.getProposedImportance(),
-                tweak.hasSensitiveContent()
-        );
-        assertNotEquals(nru, nru2);
-    }
-
     @Test
     public void testLegacyIcons_preM() {
         TestListenerService service = new TestListenerService();
@@ -275,7 +196,6 @@
         assertNull(n.largeIcon);
     }
 
-
     // Test data
 
     private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
@@ -461,48 +381,6 @@
         }
     }
 
-    private void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) {
-        detailedAssertEquals(a.getRankingMap(), b.getRankingMap());
-    }
-
-    private void detailedAssertEquals(String comment, Ranking a, Ranking b) {
-        assertEquals(comment, a.getKey(), b.getKey());
-        assertEquals(comment, a.getRank(), b.getRank());
-        assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter());
-        assertEquals(comment, a.getLockscreenVisibilityOverride(), b.getLockscreenVisibilityOverride());
-        assertEquals(comment, a.getSuppressedVisualEffects(), b.getSuppressedVisualEffects());
-        assertEquals(comment, a.getImportance(), b.getImportance());
-        assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation());
-        assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey());
-        assertEquals(comment, a.getChannel(), b.getChannel());
-        assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople());
-        assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria());
-        assertEquals(comment, a.canShowBadge(), b.canShowBadge());
-        assertEquals(comment, a.getUserSentiment(), b.getUserSentiment());
-        assertEquals(comment, a.isSuspended(), b.isSuspended());
-        assertEquals(comment, a.getLastAudiblyAlertedMillis(), b.getLastAudiblyAlertedMillis());
-        assertEquals(comment, a.isNoisy(), b.isNoisy());
-        assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
-        assertEquals(comment, a.canBubble(), b.canBubble());
-        assertEquals(comment, a.isConversation(), b.isConversation());
-        assertEquals(comment, a.getConversationShortcutInfo().getId(),
-                b.getConversationShortcutInfo().getId());
-        assertActionsEqual(a.getSmartActions(), b.getSmartActions());
-        assertEquals(a.getProposedImportance(), b.getProposedImportance());
-        assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent());
-    }
-
-    private void detailedAssertEquals(RankingMap a, RankingMap b) {
-        Ranking arank = new Ranking();
-        Ranking brank = new Ranking();
-        assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys());
-        for (String key : a.getOrderedKeys()) {
-            a.getRanking(key, arank);
-            b.getRanking(key, brank);
-            detailedAssertEquals("ranking for key <" + key + ">", arank, brank);
-        }
-    }
-
     public static class TestListenerService extends NotificationListenerService {
         private final IBinder binder = new LocalBinder();
         public int targetSdk = 0;
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 91129a1..6792cfe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -71,6 +71,7 @@
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -84,7 +85,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -207,6 +207,7 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.permission.PermissionManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -318,6 +319,7 @@
     private static final int UID_HEADLESS = 1_000_000;
     private static final int TOAST_DURATION = 2_000;
     private static final int SECONDARY_DISPLAY_ID = 42;
+    private static final int TEST_PROFILE_USERHANDLE = 12;
 
     private final int mUid = Binder.getCallingUid();
     private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
@@ -445,7 +447,7 @@
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
 
     TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
-
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
     @Mock
@@ -611,7 +613,8 @@
                 });
 
         // TODO (b/291907312): remove feature flag
-        mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, false);
+        mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
+                Flags.FLAG_POLITE_NOTIFICATIONS);
         initNMS();
     }
 
@@ -652,7 +655,7 @@
         verify(mHistoryManager).onBootPhaseAppsCanStart();
 
         // TODO b/291907312: remove feature flag
-        if (mTestFlagResolver.isEnabled(ENABLE_ATTENTION_HELPER_REFACTOR)) {
+        if (Flags.refactorAttentionHelper()) {
             mService.mAttentionHelper.setAudioManager(mAudioManager);
         } else {
             mService.setAudioManager(mAudioManager);
@@ -687,7 +690,11 @@
                 mPackageIntentReceiver = broadcastReceivers.get(i);
             }
             if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) {
-                mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+                // There may be multiple receivers, get the NMS one
+                if (broadcastReceivers.get(i).toString().contains(
+                        NotificationManagerService.class.getName())) {
+                    mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+                }
             }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
@@ -822,6 +829,12 @@
         mPackageIntentReceiver.onReceive(getContext(), intent);
     }
 
+    private void simulateProfileAvailabilityActions(String intentAction) {
+        final Intent intent = new Intent(intentAction);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
+        mUserSwitchIntentReceiver.onReceive(mContext, intent);
+    }
+
     private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
         ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
         changed.put(true, new ArrayList<>());
@@ -1679,7 +1692,7 @@
     @Test
     public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception {
         // TODO b/291907312: remove feature flag
-        mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
         // Cleanup NMS before re-initializing
         if (mService != null) {
             try {
@@ -7765,6 +7778,74 @@
         assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size());
     }
 
+    @Test
+    public void testPrioritizeSystemToasts() throws Exception {
+        // Insert non-system toasts
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+        mService.isSystemAppId = false;
+        setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
+                .thenReturn(false);
+
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        // Enqueue maximum number of toasts for test package
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) {
+            enqueueTextToast(testPackage, "Text");
+        }
+
+        // Enqueue system toast
+        final String testPackageSystem = "testPackageNameSystem";
+        mService.isSystemUid = true;
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false);
+        when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, mUserId))
+                .thenReturn(false);
+
+        enqueueToast(testPackageSystem, new TestableToastCallback());
+
+        // System toast is inserted at the front of the queue, behind current showing toast
+        assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg);
+    }
+
+    @Test
+    public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception {
+        // Insert system toasts
+        final String testPackageSystem1 = "testPackageNameSystem1";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = true;
+        setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, mUserId))
+                .thenReturn(false);
+
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        // Enqueue maximum number of toasts for test package
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) {
+            enqueueTextToast(testPackageSystem1, "Text");
+        }
+
+        // Enqueue another system toast
+        final String testPackageSystem2 = "testPackageNameSystem2";
+        mService.isSystemUid = true;
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false);
+        when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, mUserId))
+                .thenReturn(false);
+
+        enqueueToast(testPackageSystem2, new TestableToastCallback());
+
+        // System toast is inserted at the back of the queue, after the other system toasts
+        assertEquals(testPackageSystem2,
+                mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg);
+    }
+
     private void setAppInForegroundForToasts(int uid, boolean inForeground) {
         int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE;
         when(mActivityManager.getUidImportance(mUid)).thenReturn(importance);
@@ -9074,7 +9155,7 @@
     public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor()
         throws Exception {
         // TODO b/291907312: remove feature flag
-        mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
         // Cleanup NMS before re-initializing
         if (mService != null) {
             try {
@@ -12679,6 +12760,23 @@
         verify(service, times(1)).setDNDMigrationDone(user.id);
     }
 
+    @Test
+    public void testProfileUnavailableIntent() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+        verify(mWorkerHandler).post(any(Runnable.class));
+        verify(mSnoozeHelper).clearData(anyInt());
+    }
+
+
+    @Test
+    public void testManagedProfileUnavailableIntent() throws RemoteException {
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        verify(mWorkerHandler).post(any(Runnable.class));
+        verify(mSnoozeHelper).clearData(anyInt());
+    }
+
     private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
             throws RemoteException {
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
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 121e296..337dd22 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -91,7 +91,6 @@
     private static final Multimap<Class<?>, String> KNOWN_BAD =
             ImmutableMultimap.<Class<?>, String>builder()
                     .put(Person.Builder.class, "setUri") // TODO: b/281044385
-                    .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
                     .build();
 
     // Types that we can't really produce. No methods receiving these parameters will be invoked.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 51b9c17..47f15b8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -18,6 +18,7 @@
 import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
 import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
 
+import static com.google.common.truth.Truth.assertThat;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -486,6 +487,29 @@
     }
 
     @Test
+    public void testRepostAll() throws Exception {
+        final int profileId = 11;
+        final int otherUserId = 2;
+        IntArray userIds = new IntArray();
+        userIds.add(UserHandle.USER_SYSTEM);
+        userIds.add(profileId);
+        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+        NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+        NotificationRecord r3 = getNotificationRecord("pkg", 3, "three", UserHandle.of(profileId));
+        NotificationRecord r4 = getNotificationRecord("pkg", 4, "four", UserHandle.of(otherUserId));
+        mSnoozeHelper.snooze(r,  1000);
+        mSnoozeHelper.snooze(r2, 1000);
+        mSnoozeHelper.snooze(r3, 1000);
+        mSnoozeHelper.snooze(r4, 1000);
+
+        mSnoozeHelper.repostAll(userIds);
+
+        verify(mCallback, times(3)).repost(anyInt(), any(), anyBoolean());
+        // All notifications were reposted, except the one for otherUserId
+        assertThat(mSnoozeHelper.getSnoozed()).containsExactly(r4);
+    }
+
+    @Test
     public void testGetSnoozedBy() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index ca5cfa5..6f37967 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -27,6 +27,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "flag-junit",
         "frameworks-base-testutils",
         "frameworks-services-vibrator-testutils",
         "junit",
@@ -34,6 +35,7 @@
         "platform-test-annotations",
         "service-permission.stubs.system_server",
         "services.core",
+        "flag-junit",
     ],
 
     platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index bc826a3..04158c4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -31,6 +31,8 @@
 import android.content.res.Resources;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AtomicFile;
 import android.util.SparseArray;
 
@@ -49,6 +51,8 @@
 import java.io.FileOutputStream;
 
 public class HapticFeedbackCustomizationTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     // Pairs of valid vibration XML along with their equivalent VibrationEffect.
@@ -77,6 +81,7 @@
     @Before
     public void setUp() {
         when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
     }
 
     @Test
@@ -87,6 +92,21 @@
     }
 
     @Test
+    public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+        // Valid customization XML.
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        setupCustomizationFile(xml);
+
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+                .isNull();
+    }
+
+    @Test
     public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 0003555..3d0dca0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -16,20 +16,24 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
 import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
 import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.VibrationEffect.EFFECT_CLICK;
 import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
 import static android.os.VibrationEffect.EFFECT_TICK;
 import static android.view.HapticFeedbackConstants.CLOCK_TICK;
 import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
+import static android.view.HapticFeedbackConstants.KEYBOARD_TAP;
 import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
-import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
 import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
 import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
 import static android.view.HapticFeedbackConstants.SCROLL_TICK;
-
+import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -42,9 +46,10 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AtomicFile;
 import android.util.SparseArray;
-import android.view.flags.FeatureFlags;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -62,6 +67,8 @@
 public class HapticFeedbackVibrationProviderTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final VibrationEffect PRIMITIVE_TICK_EFFECT =
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
     private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
@@ -69,11 +76,15 @@
 
     private static final int[] SCROLL_FEEDBACK_CONSTANTS =
             new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
+    private static final int[] KEYBOARD_FEEDBACK_CONSTANTS =
+            new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE};
+
+    private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f;
+
     private Context mContext = InstrumentationRegistry.getContext();
     private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
 
     @Mock private Resources mResourcesMock;
-    @Mock private FeatureFlags mViewFeatureFlags;
 
     @Test
     public void testNonExistentCustomization_useDefault() throws Exception {
@@ -214,6 +225,62 @@
     }
 
     @Test
+    public void testKeyboardHaptic_noFixedAmplitude_defaultVibrationReturned() {
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT);
+        customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
+
+        // Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
+        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(PRIMITIVE_TICK_EFFECT);
+
+        // Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
+        hapticProvider = createProviderWithDefaultCustomizations();
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+    }
+
+    @Test
+    public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+    }
+
+    @Test
+    public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(VibrationEffect.startComposition()
+                        .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
+                        .compose());
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.startComposition()
+                        .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
+                        .compose());
+    }
+
+    @Test
     public void testVibrationAttribute_forNotBypassingIntensitySettings() {
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
@@ -235,7 +302,7 @@
 
     @Test
     public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
-        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
+        mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
@@ -248,7 +315,7 @@
 
     @Test
     public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
-        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(false);
+        mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
@@ -259,14 +326,71 @@
         }
     }
 
+    @Test
+    public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
+                    .that(attrs.getCategory()).isEqualTo(0);
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
+                    .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD);
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(-1);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+                    + effectId)
+                    .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+                    + effectId)
+                    .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
+        }
+    }
+
     private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
         return createProvider(/* customizations= */ null);
     }
 
     private HapticFeedbackVibrationProvider createProvider(
             SparseArray<VibrationEffect> customizations) {
-        return new HapticFeedbackVibrationProvider(
-            mResourcesMock, mVibratorInfo, customizations, mViewFeatureFlags);
+        return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
     }
 
     private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
@@ -287,6 +411,11 @@
                 .thenReturn(vibrationPattern);
     }
 
+    private void mockKeyboardVibrationFixedAmplitude(float amplitude) {
+        when(mResourcesMock.getFloat(R.dimen.config_keyboardHapticFeedbackFixedAmplitude))
+                .thenReturn(amplitude);
+    }
+
     private void setupCustomizationFile(String xml) throws Exception {
         File file = new File(mContext.getCacheDir(), "test.xml");
         file.createNewFile();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 1ae0966..7a2bb5a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -69,7 +69,11 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
 import android.os.vibrator.VibrationConfig;
+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.util.ArraySet;
 import android.view.Display;
@@ -95,6 +99,9 @@
 
 public class VibrationSettingsTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int UID = 1;
     private static final int VIRTUAL_DISPLAY_ID = 1;
     private static final String SYSUI_PACKAGE_NAME = "sysui";
@@ -606,6 +613,47 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+    public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+
+        // Keyboard touch ignored.
+        assertVibrationIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .build(),
+                Vibration.Status.IGNORED_FOR_SETTINGS);
+
+        // General touch and keyboard touch with bypass flag not ignored.
+        assertVibrationNotIgnoredForUsage(USAGE_TOUCH);
+        assertVibrationNotIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                        .build());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+    public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+
+        // General touch ignored.
+        assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+        // Keyboard touch not ignored.
+        assertVibrationNotIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .build());
+    }
+
+    @Test
     public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
         // Vibrations from the primary display is never ignored regardless of the creation and
         // removal of virtual displays and of the changes of apps running on virtual displays.
@@ -895,6 +943,14 @@
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
+    private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs,
+            Vibration.Status expectedStatus) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+                Display.DEFAULT_DISPLAY, null, null);
+        assertEquals(errorMessageForAttributes(attrs), expectedStatus,
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
+    }
+
     private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
         assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0);
     }
@@ -919,10 +975,21 @@
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
+    private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+                Display.DEFAULT_DISPLAY, null, null);
+        assertNull(errorMessageForAttributes(attrs),
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
+    }
+
     private String errorMessageForUsage(int usage) {
         return "Error for usage " + VibrationAttributes.usageToString(usage);
     }
 
+    private String errorMessageForAttributes(VibrationAttributes attrs) {
+        return "Error for attributes " + attrs;
+    }
+
     private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) {
         when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity);
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 40e0e84..3dfaed6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -82,6 +82,7 @@
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -89,7 +90,7 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
-import android.view.flags.FeatureFlags;
+import android.view.flags.Flags;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -155,6 +156,8 @@
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private VibratorManagerService.NativeWrapper mNativeWrapperMock;
     @Mock
@@ -175,8 +178,6 @@
     private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
     @Mock
     private AudioManager mAudioManagerMock;
-    @Mock
-    private FeatureFlags mViewFeatureFlags;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -326,8 +327,7 @@
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
                             Resources resources, VibratorInfo vibratorInfo) {
                         return new HapticFeedbackVibrationProvider(
-                                resources, vibratorInfo, mHapticFeedbackVibrationMap,
-                                mViewFeatureFlags);
+                                resources, vibratorInfo, mHapticFeedbackVibrationMap);
                     }
                 });
         return mService;
@@ -1354,7 +1354,7 @@
         denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
         denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
         // Flag override to enable the scroll feedack constants to bypass interruption policies.
-        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
         mHapticFeedbackVibrationMap.put(
                 HapticFeedbackConstants.SCROLL_TICK,
                 VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index af39b2f..1b8d746 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -60,6 +60,7 @@
         "truth",
         "testables",
         "hamcrest-library",
+        "flag-junit",
         "platform-compat-test-rules",
         "CtsSurfaceValidatorLib",
         "service-sdksandbox.impl",
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 8db09f9..270d5df 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,7 +46,6 @@
 import static java.util.Collections.unmodifiableMap;
 
 import android.content.Context;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.view.InputDevice;
@@ -99,10 +98,6 @@
      *      settings values.
      */
     protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         doReturn(mSettingsProviderRule.mockContentResolver(mContext))
                 .when(mContext).getContentResolver();
         mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
@@ -208,5 +203,6 @@
                 mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
             }
         }
+        mPhoneWindowManager.dispatchAllPendingEvents();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index d388db8..f2721a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -25,10 +25,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Looper;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -38,7 +40,9 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -57,6 +61,7 @@
     private CountDownLatch mLongPressed = new CountDownLatch(1);
     private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
     private CountDownLatch mMultiPressed = new CountDownLatch(1);
+    private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>();
 
     private final Instrumentation mInstrumentation = getInstrumentation();
     private final Context mContext = mInstrumentation.getTargetContext();
@@ -76,7 +81,7 @@
     public void setUp() {
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mDetector = SingleKeyGestureDetector.get(mContext);
+                    mDetector = SingleKeyGestureDetector.get(mContext, Looper.myLooper());
                     initSingleKeyGestureRules();
                 });
 
@@ -134,6 +139,11 @@
                         assertTrue(mMaxMultiPressCount >= count);
                         assertEquals(mExpectedMultiPressCount, count);
                     }
+
+                    @Override
+                    void onKeyUp(long eventTime, int multiPressCount) {
+                        mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
+                    }
                 });
 
         mDetector.addRule(
@@ -167,12 +177,27 @@
                     }
 
                     @Override
+                    void onKeyUp(long eventTime, int multiPressCount) {
+                        mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
+                    }
+
+                    @Override
                     void onLongPress(long downTime) {
                         mLongPressed.countDown();
                     }
                 });
     }
 
+    private static class KeyUpData {
+        public final int keyCode;
+        public final int pressCount;
+
+        KeyUpData(int keyCode, int pressCount) {
+            this.keyCode = keyCode;
+            this.pressCount = pressCount;
+        }
+    }
+
     private void pressKey(int keyCode, long pressTime) {
         pressKey(keyCode, pressTime, true /* interactive */);
     }
@@ -250,6 +275,21 @@
     }
 
     @Test
+    public void testOnKeyUp() throws InterruptedException {
+        pressKey(KEYCODE_POWER, 0 /* pressTime */);
+
+        verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+    }
+
+    private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)
+            throws InterruptedException {
+        KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS);
+        assertNotNull(keyUpData);
+        assertEquals(expectedKeyCode, keyUpData.keyCode);
+        assertEquals(expectedMultiPressCount, keyUpData.pressCount);
+    }
+
+    @Test
     public void testNonInteractive() throws InterruptedException {
         // Disallow short press behavior from non interactive.
         mAllowNonInteractiveForPress = false;
@@ -314,6 +354,33 @@
     }
 
     @Test
+    public void testOnKeyUp_Pressure() throws InterruptedException {
+        final HandlerThread handlerThread =
+                new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
+        handlerThread.start();
+        Handler newHandler = new Handler(handlerThread.getLooper());
+        try {
+            // To make sure we won't get any unexpected multi-press count.
+            for (int i = 0; i < 5; i++) {
+                newHandler.runWithScissors(
+                        () -> {
+                            pressKey(KEYCODE_POWER, 0 /* pressTime */);
+                            pressKey(KEYCODE_POWER, 0 /* pressTime */);
+                        },
+                        mWaitTimeout);
+                newHandler.runWithScissors(
+                        () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout);
+
+                verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+                verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */);
+                verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */);
+            }
+        } finally {
+            handlerThread.quitSafely();
+        }
+    }
+
+    @Test
     public void testUpdateRule() throws InterruptedException {
         // Power key rule doesn't allow the long press gesture.
         mLongPressOnPowerBehavior = false;
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 2244dbe..e26260a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -78,6 +78,7 @@
 import android.os.UserHandle;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
+import android.os.test.TestLooper;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.util.FeatureFlagUtils;
@@ -117,7 +118,6 @@
 import java.util.function.Supplier;
 
 class TestPhoneWindowManager {
-    private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
     private static final long TEST_SINGLE_KEY_DELAY_MILLIS
             = SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
 
@@ -160,12 +160,13 @@
     @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
 
     private StaticMockitoSession mMockitoSession;
+    private TestLooper mTestLooper = new TestLooper();
     private HandlerThread mHandlerThread;
     private Handler mHandler;
 
     private class TestInjector extends PhoneWindowManager.Injector {
         TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
-            super(context, funcs);
+            super(context, funcs, mTestLooper.getLooper());
         }
 
         AccessibilityShortcutController getAccessibilityShortcutController(
@@ -184,12 +185,10 @@
 
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
         MockitoAnnotations.initMocks(this);
-        mHandlerThread = new HandlerThread("fake window manager");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        mHandler = new Handler(mTestLooper.getLooper());
         mContext = mockingDetails(context).isSpy() ? context : spy(context);
-        mHandler.runWithScissors(() -> setUp(supportSettingsUpdate),  0 /* timeout */);
-        waitForIdle();
+        setUp(supportSettingsUpdate);
+        mTestLooper.dispatchAll();
     }
 
     private void setUp(boolean supportSettingsUpdate) {
@@ -301,12 +300,15 @@
     }
 
     void tearDown() {
-        mHandlerThread.quitSafely();
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         Mockito.reset(mPhoneWindowManager);
         mMockitoSession.finishMocking();
     }
 
+    void dispatchAllPendingEvents() {
+        mTestLooper.dispatchAll();
+    }
+
     // Override accessibility setting and perform function.
     private void overrideLaunchAccessibility() {
         doReturn(true).when(mAccessibilityShortcutController)
@@ -327,10 +329,6 @@
         mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
     }
 
-    void waitForIdle() {
-        mHandler.runWithScissors(() -> { }, 0 /* timeout */);
-    }
-
     /**
      * Below functions will override the setting or the policy behavior.
      */
@@ -451,6 +449,7 @@
         doNothing().when(mPhoneWindowManager).sendCloseSystemWindows();
         doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
         doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+        doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class));
     }
 
     void overrideSearchManager(SearchManager searchManager) {
@@ -504,68 +503,61 @@
      * Below functions will check the policy behavior could be invoked.
      */
     void assertTakeScreenshotCalled() {
-        waitForIdle();
-        verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
-                .takeScreenshot(anyInt(), anyInt());
+        mTestLooper.dispatchAll();
+        verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
     }
 
     void assertShowGlobalActionsCalled() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPhoneWindowManager).showGlobalActions();
-        verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
-                .showDialog(anyBoolean(), anyBoolean());
-        verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS))
-                .userActivity(anyLong(), anyBoolean());
+        verify(mGlobalActions).showDialog(anyBoolean(), anyBoolean());
+        verify(mPowerManager).userActivity(anyLong(), anyBoolean());
     }
 
     void assertVolumeMute() {
-        waitForIdle();
-        verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
-                .silenceRingerModeInternal(eq("volume_hush"));
+        mTestLooper.dispatchAll();
+        verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush"));
     }
 
     void assertAccessibilityKeychordCalled() {
-        waitForIdle();
-        verify(mAccessibilityShortcutController,
-                timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
+        mTestLooper.dispatchAll();
+        verify(mAccessibilityShortcutController).performAccessibilityShortcut();
     }
 
     void assertDreamRequest() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mDreamManagerInternal).requestDream();
     }
 
     void assertPowerSleep() {
-        waitForIdle();
-        verify(mPowerManager,
-                timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
+        mTestLooper.dispatchAll();
+        verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
     }
 
     void assertPowerWakeUp() {
-        waitForIdle();
-        verify(mPowerManager,
-                timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
+        mTestLooper.dispatchAll();
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
     }
 
     void assertNoPowerSleep() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
     }
 
     void assertCameraLaunch() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         // GestureLauncherService should receive interceptPowerKeyDown twice.
         verify(mGestureLauncherService, times(2))
                 .interceptPowerKeyDown(any(), anyBoolean(), any());
     }
 
     void assertSearchManagerLaunchAssist() {
-        waitForIdle();
-        verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
+        mTestLooper.dispatchAll();
+        verify(mSearchManager).launchAssist(any());
     }
 
     void assertLaunchCategory(String category) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         try {
             verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
@@ -578,17 +570,17 @@
     }
 
     void assertShowRecentApps() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
     }
 
     void assertStatusBarStartAssist() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).startAssist(any());
     }
 
     void assertSwitchKeyboardLayout(int direction) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
             verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
             verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
@@ -599,7 +591,7 @@
     }
 
     void assertTakeBugreport() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
                 any(), anyInt(), any(), any());
@@ -607,17 +599,17 @@
     }
 
     void assertTogglePanel() throws RemoteException {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPhoneWindowManager.mStatusBarService).togglePanel();
     }
 
     void assertToggleShortcutsMenu() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
     }
 
     void assertToggleCapsLock() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mInputManagerInternal).toggleCapsLock(anyInt());
     }
 
@@ -642,12 +634,12 @@
     }
 
     void assertGoToHomescreen() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
     }
 
     void assertOpenAllAppView() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
                 .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -655,13 +647,13 @@
     }
 
     void assertNotOpenAllAppView() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
                 .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
     }
 
     void assertActivityTargetLaunched(ComponentName targetActivity) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
                 .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -670,7 +662,7 @@
 
     void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
             int expectedKey, int expectedModifierState, String errorMsg) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                         vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
                         expectedModifierState), description(errorMsg));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 40ac7b1c..bdbfb7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2627,6 +2627,13 @@
         // Can specify orientation if the current orientation candidate is orientation behind.
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
                 activity.getOrientation(SCREEN_ORIENTATION_BEHIND));
+
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
+                .setCreateTask(true).build();
+        assertFalse(translucentActivity.providesOrientation());
+        translucentActivity.setOccludesParent(true);
+        assertTrue(translucentActivity.providesOrientation());
     }
 
     @Test
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 8e7ba70..dd7dec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -130,12 +130,22 @@
         // verify if back animation would start.
         assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
 
-        // reset drawing status
+        // reset drawing status to test translucent activity
         backNavigationInfo.onBackNavigationFinished(false);
         mBackNavigationController.clearBackAnimations();
-        topTask.forAllWindows(w -> {
-            makeWindowVisibleAndDrawn(w);
-        }, true);
+        final ActivityRecord topActivity = topTask.getTopMostActivity();
+        makeWindowVisibleAndDrawn(topActivity.findMainWindow());
+        // simulate translucent
+        topActivity.setOccludesParent(false);
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+        // reset drawing status to test keyguard occludes
+        topActivity.setOccludesParent(true);
+        backNavigationInfo.onBackNavigationFinished(false);
+        mBackNavigationController.clearBackAnimations();
+        makeWindowVisibleAndDrawn(topActivity.findMainWindow());
         setupKeyguardOccluded();
         backNavigationInfo = startBackNavigation();
         assertThat(typeToString(backNavigationInfo.getType()))
@@ -201,9 +211,7 @@
         // reset drawing status
         backNavigationInfo.onBackNavigationFinished(false);
         mBackNavigationController.clearBackAnimations();
-        testCase.recordFront.forAllWindows(w -> {
-            makeWindowVisibleAndDrawn(w);
-        }, true);
+        makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());
         setupKeyguardOccluded();
         backNavigationInfo = startBackNavigation();
         assertThat(typeToString(backNavigationInfo.getType()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index d2eb1cc..78566fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -84,31 +84,49 @@
     private final ContentRecordingSession mWaitingDisplaySession =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
     private ContentRecordingSession mTaskSession;
-    private static Point sSurfaceSize;
+    private Point mSurfaceSize;
     private ContentRecorder mContentRecorder;
     @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
     private SurfaceControl mRecordedSurface;
 
+    private boolean mHandleAnisotropicDisplayMirroring = false;
+
     @Before public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        // GIVEN SurfaceControl can successfully mirror the provided surface.
-        sSurfaceSize = new Point(
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-        mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
-
         doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
 
-        // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
+        // Skip unnecessary operations of relayout.
+        spyOn(mWm.mWindowPlacerLocked);
+        doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
+    }
+
+    private void defaultInit() {
+        createContentRecorder(createDefaultDisplayInfo());
+    }
+
+    private DisplayInfo createDefaultDisplayInfo() {
+        return createDisplayInfo(mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+    }
+
+    private DisplayInfo createDisplayInfo(int width, int height) {
+        // GIVEN SurfaceControl can successfully mirror the provided surface.
+        mSurfaceSize = new Point(width, height);
+        mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
+
         DisplayInfo displayInfo = mDisplayInfo;
-        displayInfo.logicalWidth = sSurfaceSize.x;
-        displayInfo.logicalHeight = sSurfaceSize.y;
+        displayInfo.logicalWidth = width;
+        displayInfo.logicalHeight = height;
         displayInfo.state = STATE_ON;
+        return displayInfo;
+    }
+
+    private void createContentRecorder(DisplayInfo displayInfo) {
         mVirtualDisplayContent = createNewDisplay(displayInfo);
         final int displayId = mVirtualDisplayContent.getDisplayId();
         mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
-                mMediaProjectionManagerWrapper);
+                mMediaProjectionManagerWrapper, mHandleAnisotropicDisplayMirroring);
         spyOn(mVirtualDisplayContent);
 
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -124,14 +142,11 @@
         // GIVEN a session is waiting for the user to review consent.
         mWaitingDisplaySession.setVirtualDisplayId(displayId);
         mWaitingDisplaySession.setWaitingForConsent(true);
-
-        // Skip unnecessary operations of relayout.
-        spyOn(mWm.mWindowPlacerLocked);
-        doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
     }
 
     @Test
     public void testIsCurrentlyRecording() {
+        defaultInit();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
 
         mContentRecorder.updateRecording();
@@ -140,6 +155,7 @@
 
     @Test
     public void testUpdateRecording_display() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -147,6 +163,7 @@
 
     @Test
     public void testUpdateRecording_display_invalidDisplayIdToMirror() {
+        defaultInit();
         ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
                 INVALID_DISPLAY);
         mContentRecorder.setContentRecordingSession(session);
@@ -156,6 +173,7 @@
 
     @Test
     public void testUpdateRecording_display_noDisplayContentToMirror() {
+        defaultInit();
         doReturn(null).when(
                 mWm.mRoot).getDisplayContent(anyInt());
         mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -165,6 +183,7 @@
 
     @Test
     public void testUpdateRecording_task_nullToken() {
+        defaultInit();
         ContentRecordingSession session = mTaskSession;
         session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
         session.setTokenToRecord(null);
@@ -176,6 +195,7 @@
 
     @Test
     public void testUpdateRecording_task_noWindowContainer() {
+        defaultInit();
         // Use the window container token of the DisplayContent, rather than task.
         ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession(
                 new WindowContainer.RemoteToken(mDisplayContent));
@@ -187,6 +207,7 @@
 
     @Test
     public void testUpdateRecording_wasPaused() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -197,6 +218,7 @@
 
     @Test
     public void testUpdateRecording_waitingForConsent() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -209,6 +231,7 @@
 
     @Test
     public void testOnConfigurationChanged_neverRecording() {
+        defaultInit();
         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
 
         verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
@@ -218,6 +241,7 @@
 
     @Test
     public void testOnConfigurationChanged_resizesSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         // Ensure a different orientation when we check if something has changed.
@@ -234,13 +258,14 @@
 
     @Test
     public void testOnConfigurationChanged_resizesVirtualDisplay() {
+        defaultInit();
         final int newWidth = 55;
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
         // The user rotates the device, so the host app resizes the virtual display for the capture.
-        resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
-        resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+        resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y);
+        resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y);
         mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
 
         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -251,6 +276,7 @@
 
     @Test
     public void testOnConfigurationChanged_rotateVirtualDisplay() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -271,12 +297,13 @@
      */
     @Test
     public void testOnConfigurationChanged_resizeSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
         // Resize the output surface.
-        final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
-                Math.round(sSurfaceSize.y * 2));
+        final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f),
+                Math.round(mSurfaceSize.y * 2));
         doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
                 anyInt());
         mContentRecorder.onConfigurationChanged(
@@ -292,6 +319,7 @@
 
     @Test
     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
@@ -314,6 +342,7 @@
 
     @Test
     public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+        defaultInit();
         mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
 
         final int minWidth = 222;
@@ -351,6 +380,7 @@
 
     @Test
     public void testTaskWindowingModeChanged_pip_stopsRecording() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -368,6 +398,7 @@
 
     @Test
     public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +415,7 @@
 
     @Test
     public void testStartRecording_notifiesCallback_taskSession() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -396,6 +428,7 @@
 
     @Test
     public void testStartRecording_notifiesCallback_displaySession() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
@@ -408,6 +441,7 @@
 
     @Test
     public void testStartRecording_taskInPIP_recordingNotStarted() {
+        defaultInit();
         // GIVEN a task is in PIP.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -421,6 +455,7 @@
 
     @Test
     public void testStartRecording_taskInSplit_recordingStarted() {
+        defaultInit();
         // GIVEN a task is in PIP.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -434,6 +469,7 @@
 
     @Test
     public void testStartRecording_taskInFullscreen_recordingStarted() {
+        defaultInit();
         // GIVEN a task is in PIP.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -447,6 +483,7 @@
 
     @Test
     public void testOnVisibleRequestedChanged_notifiesCallback() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -471,6 +508,7 @@
 
     @Test
     public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+        defaultInit();
         // WHEN a recording is not ongoing.
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
 
@@ -493,6 +531,7 @@
 
     @Test
     public void testPauseRecording_pausesRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -502,12 +541,14 @@
 
     @Test
     public void testPauseRecording_neverRecording() {
+        defaultInit();
         mContentRecorder.pauseRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
     }
 
     @Test
     public void testStopRecording_stopsRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -517,12 +558,14 @@
 
     @Test
     public void testStopRecording_neverRecording() {
+        defaultInit();
         mContentRecorder.stopRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
     }
 
     @Test
     public void testRemoveTask_stopsRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
@@ -533,6 +576,7 @@
 
     @Test
     public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
         mContentRecorder.setContentRecordingSession(null);
@@ -541,6 +585,7 @@
 
     @Test
     public void testUpdateMirroredSurface_capturedAreaResized() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -548,9 +593,9 @@
         // WHEN attempting to mirror on the virtual display, and the captured content is resized.
         float xScale = 0.7f;
         float yScale = 2f;
-        Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale),
-                Math.round(sSurfaceSize.y * yScale));
-        mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize);
+        Rect displayAreaBounds = new Rect(0, 0, Math.round(mSurfaceSize.x * xScale),
+                Math.round(mSurfaceSize.y * yScale));
+        mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, mSurfaceSize);
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
 
         // THEN content in the captured DisplayArea is scaled to fit the surface size.
@@ -558,7 +603,7 @@
                 1.0f / yScale);
         // THEN captured content is positioned in the centre of the output surface.
         int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
-        int xInset = (sSurfaceSize.x - scaledWidth) / 2;
+        int xInset = (mSurfaceSize.x - scaledWidth) / 2;
         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
         // THEN the resize callback is notified.
         verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
@@ -566,7 +611,131 @@
     }
 
     @Test
+    public void testUpdateMirroredSurface_isotropicPixel() {
+        mHandleAnisotropicDisplayMirroring = false;
+        DisplayInfo displayInfo = createDefaultDisplayInfo();
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1, 0, 0, 1);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_compressY() {
+        mHandleAnisotropicDisplayMirroring = true;
+        DisplayInfo displayInfo = createDefaultDisplayInfo();
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 1f;
+        float yScale = 0.5f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+                Math.round(0.25 * mSurfaceSize.y));
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_compressX() {
+        mHandleAnisotropicDisplayMirroring = true;
+        DisplayInfo displayInfo = createDefaultDisplayInfo();
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 0.5f;
+        float yScale = 1f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+                Math.round(0.25 * mSurfaceSize.x), 0);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_scaleOnX() {
+        mHandleAnisotropicDisplayMirroring = true;
+        int width = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+        int height = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+        DisplayInfo displayInfo = createDisplayInfo(width, height);
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 2f;
+        float yScale = 4f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+                inputDisplayInfo.logicalHeight);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_scaleOnY() {
+        mHandleAnisotropicDisplayMirroring = true;
+        int width = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+        int height = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+        DisplayInfo displayInfo = createDisplayInfo(width, height);
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 4f;
+        float yScale = 2f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+                inputDisplayInfo.logicalWidth, 0);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas() {
+        mHandleAnisotropicDisplayMirroring = true;
+        int width = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width() / 2;
+        int height = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height() / 2;
+        DisplayInfo displayInfo = createDisplayInfo(width, height);
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = 2f * inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 0.5f;
+        float yScale = 0.25f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+                (mSurfaceSize.y - height / 2) / 2);
+    }
+
+    @Test
     public void testDisplayContentUpdatesRecording_withoutSurface() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
@@ -585,6 +754,7 @@
 
     @Test
     public void testDisplayContentUpdatesRecording_withSurface() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
@@ -602,12 +772,13 @@
 
     @Test
     public void testDisplayContentUpdatesRecording_displayMirroring() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
 
         // GIVEN SurfaceControl can successfully mirror the provided surface.
-        surfaceControlMirrors(sSurfaceSize);
+        surfaceControlMirrors(mSurfaceSize);
         // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
         // call in the test. We need to spy on the DC before updateRecording is called or we can't
         // verify setDisplayMirroring is called
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 233a207..9f58491 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -18,16 +18,20 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.utils.LastCallVerifier.lastCall;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
 
 import android.graphics.Rect;
@@ -35,8 +39,8 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
-import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.utils.MockAnimationAdapter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -118,102 +122,147 @@
         }
     }
 
-    private MockSurfaceBuildingContainer mHost;
-    private Dimmer mDimmer;
-    private SurfaceControl.Transaction mTransaction;
-    private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+    static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+                SurfaceAnimationRunner runner) {
+            return sTestAnimation;
+        }
+    }
 
-    private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
+    private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
         @Override
         public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden, @AnimationType int type) {
+                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
             surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
         }
     }
 
+    private MockSurfaceBuildingContainer mHost;
+    private Dimmer mDimmer;
+    private SurfaceControl.Transaction mTransaction;
+    private TestWindowContainer mChild;
+    private static AnimationAdapter sTestAnimation;
+    private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
+
     @Before
     public void setUp() throws Exception {
         mHost = new MockSurfaceBuildingContainer(mWm);
-        mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
         mTransaction = spy(StubTransaction.class);
-        mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
+        mChild = new TestWindowContainer(mWm);
+        if (Dimmer.DIMMER_REFACTOR) {
+            sTestAnimation = spy(new MockAnimationAdapter());
+            mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
+        } else {
+            sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
+            mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
+        }
     }
 
     @Test
     public void testUpdateDimsAppliesCrop() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+        mHost.addChild(mChild, 0);
 
-        final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
+        mDimmer.adjustAppearance(mChild, 1, 1);
+        mDimmer.adjustRelativeLayer(mChild, -1);
 
         int width = 100;
         int height = 300;
-        mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+        mDimmer.getDimBounds().set(0, 0, width, height);
         mDimmer.updateDims(mTransaction);
 
-        verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
-        verify(mTransaction).show(getDimLayer());
+        verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height);
+        verify(mTransaction).show(mDimmer.getDimLayer());
     }
 
     @Test
-    public void testDimAboveWithChildCreatesSurfaceAboveChild() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+        assumeTrue(Dimmer.DIMMER_REFACTOR);
+        final float alpha = 0.7f;
+        final int blur = 50;
+        mHost.addChild(mChild, 0);
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
-        final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        SurfaceControl dimLayer = getDimLayer();
+        assertNotNull("Dimmer should have created a surface", dimLayer);
+
+        mDimmer.updateDims(mTransaction);
+        verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1);
+        verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+        verify(mTransaction).setBackgroundBlurRadius(dimLayer, blur);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
+        assumeFalse(Dimmer.DIMMER_REFACTOR);
+        final float alpha = 0.7f;
+        mHost.addChild(mChild, 0);
+        mDimmer.adjustAppearance(mChild, alpha, 20);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
 
         verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1);
+        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
     }
 
     @Test
-    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+        assumeTrue(Dimmer.DIMMER_REFACTOR);
+        mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimBelow(child, alpha, 0);
-        SurfaceControl dimLayer = getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
+        final int blur = 50;
+        // Dim once
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+        // Reset, and don't dim
+        mDimmer.resetDimStates();
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+        verify(mTransaction).show(dimLayer);
+        verify(mTransaction).remove(dimLayer);
     }
 
     @Test
-    public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
+        assumeFalse(Dimmer.DIMMER_REFACTOR);
+        mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        SurfaceControl dimLayer = getDimLayer();
+        mDimmer.adjustAppearance(mChild, alpha, 20);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.resetDimStates();
 
         mDimmer.updateDims(mTransaction);
-        verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
-                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+        verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
+                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
+                anyBoolean(),
                 eq(ANIMATION_TYPE_DIMMER));
         verify(mHost.getPendingTransaction()).remove(dimLayer);
     }
 
     @Test
     public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+        mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        SurfaceControl dimLayer = getDimLayer();
+        final int blur = 20;
+        // Dim once
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+        // Reset and dim again
         mDimmer.resetDimStates();
-        mDimmer.dimAbove(child, alpha);
-
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction, never()).remove(dimLayer);
@@ -221,14 +270,13 @@
 
     @Test
     public void testDimUpdateWhileDimming() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
-
+        mHost.addChild(mChild, 0);
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        final Rect bounds = mDimmer.mDimState.mDimBounds;
+        mDimmer.adjustAppearance(mChild, alpha, 20);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        final Rect bounds = mDimmer.getDimBounds();
 
-        SurfaceControl dimLayer = getDimLayer();
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
         bounds.set(0, 0, 10, 10);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
@@ -242,41 +290,58 @@
     }
 
     @Test
-    public void testRemoveDimImmediately() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
-
-        mDimmer.dimAbove(child, 1);
-        SurfaceControl dimLayer = getDimLayer();
+    public void testRemoveDimImmediately_Smooth() {
+        assumeTrue(Dimmer.DIMMER_REFACTOR);
+        mHost.addChild(mChild, 0);
+        mDimmer.adjustAppearance(mChild, 1, 2);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         verify(mTransaction, times(1)).show(dimLayer);
 
-        reset(mSurfaceAnimatorStarter);
+        reset(sTestAnimation);
         mDimmer.dontAnimateExit();
         mDimmer.resetDimStates();
         mDimmer.updateDims(mTransaction);
-        verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
-                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+        verify(sTestAnimation, never()).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).remove(dimLayer);
+    }
+
+    @Test
+    public void testRemoveDimImmediately_Legacy() {
+        assumeFalse(Dimmer.DIMMER_REFACTOR);
+        mHost.addChild(mChild, 0);
+        mDimmer.adjustAppearance(mChild, 1, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+        verify(mTransaction, times(1)).show(dimLayer);
+
+        reset(sSurfaceAnimatorStarter);
+        mDimmer.dontAnimateExit();
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction);
+        verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
+                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
                 eq(ANIMATION_TYPE_DIMMER));
         verify(mTransaction).remove(dimLayer);
     }
 
     @Test
-    public void testDimmerWithBlurUpdatesTransaction() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+    public void testDimmerWithBlurUpdatesTransaction_Legacy() {
+        assumeFalse(Dimmer.DIMMER_REFACTOR);
+        mHost.addChild(mChild, 0);
 
         final int blurRadius = 50;
-        mDimmer.dimBelow(child, 0, blurRadius);
-        SurfaceControl dimLayer = getDimLayer();
+        mDimmer.adjustAppearance(mChild, 1, blurRadius);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
 
         verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
-    }
-
-    private SurfaceControl getDimLayer() {
-        return mDimmer.mDimState.mDimLayer;
+        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index cd0389d..ba31944 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -46,10 +46,13 @@
 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.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -583,6 +586,7 @@
         final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
         doReturn(mock(IBinder.class)).when(mockDisplayAreaOrganizer).asBinder();
         displayArea.mOrganizer = mockDisplayAreaOrganizer;
+        displayArea.mDisplayAreaAppearedSent = true;
         spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
         mDisplayContent.addChild(displayArea, 0);
 
@@ -687,6 +691,56 @@
         assertEquals(parent.getChildAt(0), child);
     }
 
+    @Test
+    public void testSetOrganizer() {
+        final TaskDisplayArea displayArea = createTaskDisplayArea(
+                mDisplayContent, mWm, "NewArea", FEATURE_VENDOR_FIRST);
+
+        assertNull(displayArea.mOrganizer);
+        assertFalse(displayArea.mDisplayAreaAppearedSent);
+
+        final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
+        final DisplayAreaOrganizerController controller =
+                mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController;
+        spyOn(controller);
+        doNothing().when(controller).onDisplayAreaVanished(any(), any());
+
+        displayArea.setOrganizer(organizer);
+
+        assertEquals(organizer, displayArea.mOrganizer);
+        assertTrue(displayArea.mDisplayAreaAppearedSent);
+        verify(controller).onDisplayAreaAppeared(organizer, displayArea);
+
+        // No duplicated appeared sent.
+        clearInvocations(controller);
+        displayArea.sendDisplayAreaAppeared();
+
+        verify(controller, never()).onDisplayAreaAppeared(any(), any());
+
+        // Sent info changed after appeared.
+        displayArea.sendDisplayAreaInfoChanged();
+
+        verify(controller).onDisplayAreaInfoChanged(organizer, displayArea);
+
+        // Sent info vanished after appeared.
+        displayArea.setOrganizer(null);
+
+        verify(controller).onDisplayAreaVanished(organizer, displayArea);
+        assertNull(displayArea.mOrganizer);
+        assertFalse(displayArea.mDisplayAreaAppearedSent);
+
+        // No callback until appeared sent.
+        clearInvocations(controller);
+
+        displayArea.sendDisplayAreaAppeared();
+        displayArea.sendDisplayAreaInfoChanged();
+        displayArea.sendDisplayAreaVanished(organizer);
+
+        verify(controller, never()).onDisplayAreaAppeared(any(), any());
+        verify(controller, never()).onDisplayAreaInfoChanged(any(), any());
+        verify(controller, never()).onDisplayAreaVanished(any(), any());
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) {
             super(wms, ANY, name);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5f92fd5..0d4c443 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -676,60 +676,59 @@
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
     public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
             throws Exception {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
     public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR);
+        assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
     public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
     public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
     public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_LANDSCAPE),
-                SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+        assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
     public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR);
+        assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
     public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
     }
 
     @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION})
     public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() {
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR);
+        assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
@@ -740,8 +739,8 @@
 
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
@@ -760,8 +759,8 @@
         doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
                 .isActivityEligibleForOrientationOverride(eq(mActivity));
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
@@ -780,8 +779,8 @@
         doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
                 .isActivityEligibleForOrientationOverride(eq(mActivity));
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
@@ -789,8 +788,8 @@
         spyOn(mController);
         doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER);
+        assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     @Test
@@ -799,8 +798,8 @@
         spyOn(mController);
         doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER);
+        assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
@@ -808,8 +807,8 @@
         spyOn(mController);
         doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
@@ -817,15 +816,15 @@
         spyOn(mController);
         doReturn(true).when(mController).shouldApplyUserMinAspectRatioOverride();
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_LOCKED), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_LOCKED));
 
         // unchanged if orientation is specified
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), SCREEN_ORIENTATION_LANDSCAPE);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
     }
 
     @Test
@@ -833,8 +832,8 @@
         spyOn(mController);
         doReturn(false).when(mController).shouldApplyUserMinAspectRatioOverride();
 
-        assertEquals(mController.overrideOrientationIfNeeded(
-                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
 
     // shouldApplyUser...Override
@@ -1286,16 +1285,16 @@
         mActivity = setUpActivityWithComponent();
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
-                mActivity.getParent().getConfiguration()), 1.5f, /* delta */ 0.01);
+        assertEquals(1.5f, mController.getFixedOrientationLetterboxAspectRatio(
+                mActivity.getParent().getConfiguration()), /* delta */ 0.01);
 
         spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
         doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
                 .isTreatmentEnabledForActivity(eq(mActivity));
 
-        assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
-                mActivity.getParent().getConfiguration()), mController.getSplitScreenAspectRatio(),
-                /* delta */  0.01);
+        assertEquals(mController.getSplitScreenAspectRatio(),
+                mController.getFixedOrientationLetterboxAspectRatio(
+                        mActivity.getParent().getConfiguration()), /* delta */  0.01);
     }
 
     private void mockThatProperty(String propertyName, boolean value) throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 6c48a69..9f43a17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -94,6 +94,16 @@
     }
 
     @Test
+    public void test_selectiveCloneLunchRemoteTransition() {
+        final RemoteTransition transition = mock(RemoteTransition.class);
+        final SafeActivityOptions clone = new SafeActivityOptions(
+                ActivityOptions.makeRemoteTransition(transition))
+                .selectiveCloneLaunchOptions();
+
+        assertSame(clone.getOriginalOptions().getRemoteTransition(), transition);
+    }
+
+    @Test
     public void test_getOptions() {
         // Mock everything necessary
         MockitoSession mockingSession = mockitoSession()
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 4864868..3cb4a1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
@@ -19,6 +19,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
 import android.os.Handler;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 
 import org.junit.Rule;
@@ -27,11 +28,16 @@
 
 /** The base class which provides the common rule for test classes under wm package. */
 class SystemServiceTestsBase {
-    @Rule
+    @Rule(order = 0)
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
-    @Rule
-    public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
+
+    @Rule(order = 1)
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule(order = 2)
+    public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(
+            this::onBeforeSystemServicesCreated);
 
     @WindowTestRunner.MethodWrapperRule
     public final WindowManagerGlobalLockRule mLockRule =
@@ -65,6 +71,11 @@
     }
 
     /**
+     * Called before system services are created
+     */
+    protected void onBeforeSystemServicesCreated() {}
+
+    /**
      * Make the system booted, so that {@link ActivityStack#resumeTopActivityInnerLocked} can really
      * be executed to update activity state and configuration when resuming the current top.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 7634d9f..03188f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,11 +134,20 @@
     private WindowState.PowerManagerWrapper mPowerManagerWrapper;
     private InputManagerService mImService;
     private InputChannel mInputChannel;
+    private Runnable mOnBeforeServicesCreated;
     /**
      * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
      */
     SurfaceControl.Transaction mTransaction;
 
+    public SystemServicesTestRule(Runnable onBeforeServicesCreated) {
+        mOnBeforeServicesCreated = onBeforeServicesCreated;
+    }
+
+    public SystemServicesTestRule() {
+        this(/* onBeforeServicesCreated= */ null);
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
@@ -168,6 +177,10 @@
     }
 
     private void setUp() {
+        if (mOnBeforeServicesCreated != null) {
+            mOnBeforeServicesCreated.run();
+        }
+
         // Use stubOnly() to reduce memory usage if it doesn't need verification.
         final MockSettings spyStubOnly = withSettings().stubOnly()
                 .defaultAnswer(CALLS_REAL_METHODS);
@@ -195,15 +208,18 @@
     private void setUpSystemCore() {
         doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
         doAnswer(invocation -> {
-            // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig
-            // only registers once and it doesn't reference to outside.
-            if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) {
-                mDeviceConfigListeners.add(invocation.getArgument(2));
+            if ("addOnPropertiesChangedListener".equals(invocation.getMethod().getName())) {
+                // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig
+                // only registers once and it doesn't reference to outside.
+                if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) {
+                    mDeviceConfigListeners.add(invocation.getArgument(2));
+                }
+                // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests
+                // uses splash_screen_exception_list. So still execute real registration.
             }
-            // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests
-            // uses splash_screen_exception_list. So still execute real registration.
             return invocation.callRealMethod();
-        }).when(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(), any(), any()));
+        }).when(() -> DeviceConfig.addOnPropertiesChangedListener(
+                anyString(), any(), any(DeviceConfig.OnPropertiesChangedListener.class)));
 
         mContext = getInstrumentation().getTargetContext();
         spyOn(mContext);
@@ -384,20 +400,24 @@
     }
 
     private void tearDown() {
-        for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
-            final DisplayContent dc = mWmService.mRoot.getChildAt(i);
-            // Unregister SettingsObserver.
-            dc.getDisplayPolicy().release();
-            // Unregister SensorEventListener (foldable device may register for hinge angle).
-            dc.getDisplayRotation().onDisplayRemoved();
-            if (dc.mDisplayRotationCompatPolicy != null) {
-                dc.mDisplayRotationCompatPolicy.dispose();
+        if (mWmService != null) {
+            for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
+                final DisplayContent dc = mWmService.mRoot.getChildAt(i);
+                // Unregister SettingsObserver.
+                dc.getDisplayPolicy().release();
+                // Unregister SensorEventListener (foldable device may register for hinge angle).
+                dc.getDisplayRotation().onDisplayRemoved();
+                if (dc.mDisplayRotationCompatPolicy != null) {
+                    dc.mDisplayRotationCompatPolicy.dispose();
+                }
             }
         }
 
-        // Unregister display listener from root to avoid issues with subsequent tests.
-        mContext.getSystemService(DisplayManager.class)
-                .unregisterDisplayListener(mAtmService.mRootWindowContainer);
+        if (mAtmService != null) {
+            // Unregister display listener from root to avoid issues with subsequent tests.
+            mContext.getSystemService(DisplayManager.class)
+                    .unregisterDisplayListener(mAtmService.mRootWindowContainer);
+        }
 
         for (int i = mDeviceConfigListeners.size() - 1; i >= 0; i--) {
             DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListeners.get(i));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2bf1385..c57b051 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,7 +26,9 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1655,6 +1657,128 @@
         assertEquals(frontMostTaskFragment, tf0);
     }
 
+    @Test
+    public void testApplyTransaction_reorderToBottomOfTask() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+        final Task task = createTask(mDisplayContent);
+        // Create a non-embedded Activity at the bottom.
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        final TaskFragment tf0 = createTaskFragment(task);
+        final TaskFragment tf1 = createTaskFragment(task);
+        // Create a non-embedded Activity at the top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+
+        // Ensure correct order of the children before the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+        // Reorder TaskFragment to bottom
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build();
+        mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Ensure correct order of the children after the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf0, task.getChildAt(2).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(0).asTaskFragment());
+    }
+
+    @Test
+    public void testApplyTransaction_reorderToTopOfTask() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+        final Task task = createTask(mDisplayContent);
+        // Create a non-embedded Activity at the bottom.
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        final TaskFragment tf0 = createTaskFragment(task);
+        final TaskFragment tf1 = createTaskFragment(task);
+        // Create a non-embedded Activity at the top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+
+        // Ensure correct order of the children before the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+        // Reorder TaskFragment to top
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_TOP_OF_TASK).build();
+        mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Ensure correct order of the children after the operation
+        assertEquals(tf0, task.getChildAt(3).asTaskFragment());
+        assertEquals(topActivity, task.getChildAt(2).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+    }
+
+    @Test
+    public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() {
+        testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+                OP_TYPE_REORDER_TO_BOTTOM_OF_TASK);
+    }
+
+    @Test
+    public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() {
+        testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+                OP_TYPE_REORDER_TO_TOP_OF_TASK);
+    }
+
+    private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+            @TaskFragmentOperation.OperationType int opType) {
+        final Task task = createTask(mDisplayContent);
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+        // Create a non-embedded Activity at the bottom.
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        final TaskFragment tf0 = createTaskFragment(task);
+        final TaskFragment tf1 = createTaskFragment(task);
+        // Create a non-embedded Activity at the top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+
+        // Ensure correct order of the children before the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+        // Apply reorder transaction, which is expected to fail for non-system organizer.
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                opType).build();
+        mTransaction
+                .addTaskFragmentOperation(tf0.getFragmentToken(), operation)
+                .setErrorCallbackToken(mErrorToken);
+        assertApplyTransactionAllowed(mTransaction);
+        // The pending event will be dispatched on the handler (from requestTraversal).
+        waitHandlerIdle(mWm.mAnimationHandler);
+
+        assertTaskFragmentErrorTransaction(opType, SecurityException.class);
+
+        // Ensure no change to the order of the children after the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+    }
+
     /**
      * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
      * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
@@ -1782,6 +1906,19 @@
         assertEquals(activityToken, change.getActivityToken());
     }
 
+    /** Setups an embedded TaskFragment. */
+    private TaskFragment createTaskFragment(Task task) {
+        final IBinder token = new Binder();
+        TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(token)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment);
+        return taskFragment;
+    }
+
     /** Setups an embedded TaskFragment in a PIP Task. */
     private void setupTaskFragmentInPip() {
         mTaskFragment = new TaskFragmentBuilder(mAtm)
@@ -1798,7 +1935,7 @@
     /** Setups the mock Task as the parent of the given TaskFragment. */
     private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
         doReturn(mockParent).when(taskFragment).getTask();
-        doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true))
+        doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true, true))
                 .when(mockParent).getTaskFragmentParentInfo();
 
         // Task needs to be visible
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 5205bb0..7822071 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -37,6 +37,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 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;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -687,4 +689,24 @@
         tf0.setIsolatedNav(true);
         assertTrue(tf0.isIsolatedNav());
     }
+
+    @Test
+    public void testGetDimBounds() {
+        final Task task = mTaskFragment.getTask();
+        final Rect taskBounds = task.getBounds();
+        mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10,
+                taskBounds.top + 10);
+        final Rect taskFragmentBounds = mTaskFragment.getBounds();
+
+        // Return Task bounds if dimming on parent Task.
+        final Rect dimBounds = new Rect();
+        mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+        mTaskFragment.getDimBounds(dimBounds);
+        assertEquals(taskBounds, dimBounds);
+
+        // Return TF bounds by default.
+        mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT);
+        mTaskFragment.getDimBounds(dimBounds);
+        assertEquals(taskFragmentBounds, dimBounds);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 13a4c11..8fecbb9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -96,7 +96,7 @@
     @Test
     public void testTaskRemovedFromRecents() {
         mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
-        mPersister.onTaskRemovedFromRecents(1, mTestUserId);
+        mPersister.removeSnapshot(1, mTestUserId);
         mSnapshotPersistQueue.waitForQueueEmpty();
         assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.proto").exists());
         assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.jpg").exists());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 08438c8..267bec9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -19,6 +19,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -79,7 +80,7 @@
     private ImageReader mImageReader;
     private final ArrayList<Activity> mStartedActivities = new ArrayList<>();
 
-    private static final int WAIT_TIMEOUT_MS = 5000;
+    private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER;
     private static final Object sLock = new Object();
 
     @Before
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 0c58069..435a835 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1568,6 +1568,7 @@
 
         final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord embeddedActivity = fragment.getTopMostActivity();
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
         task.moveActivityToFront(activity);
         assertEquals("Activity must be moved to front", activity, task.getTopMostActivity());
 
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 474720f..4773023 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -48,6 +48,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -60,6 +61,7 @@
 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.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -84,6 +86,7 @@
 import android.window.ITaskOrganizer;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
+import android.window.SystemPerformanceHinter;
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 
@@ -2433,6 +2436,85 @@
         assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
     }
 
+    @Test
+    public void testTransitionsTriggerPerformanceHints() {
+        assumeTrue(explicitRefreshRateHints());
+        SystemPerformanceHinter systemPerformanceHinter = mock(SystemPerformanceHinter.class);
+        final TransitionController controller = new TestTransitionController(mAtm);
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+        mSyncEngine = createTestBLASTSyncEngine();
+        controller.setSyncEngine(mSyncEngine);
+        controller.setSystemPerformanceHinter(systemPerformanceHinter);
+        SystemPerformanceHinter.HighPerfSession session = mock(
+                SystemPerformanceHinter.HighPerfSession.class);
+        doReturn(session).when(systemPerformanceHinter).startSession(anyInt(), anyInt(),
+                anyString());
+
+        final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+        final Task task = createTask(mDisplayContent,
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+        final ActivityRecord act = createActivityRecord(task);
+        act.setVisibleRequested(true);
+        act.setVisible(true);
+
+        controller.startCollectOrQueue(transitA, (deferred) -> {
+        });
+        transitA.collect(act);
+
+        verify(systemPerformanceHinter).startSession(
+                eq(SystemPerformanceHinter.HINT_SF), anyInt(), eq("Transition collected"));
+
+        transitA.start();
+        transitA.setAllReady();
+
+        // Aborting here doesn't abort the transition, it aborts the sync allowing the transition to
+        // finish successfully.
+        mSyncEngine.abort(transitA.getSyncId());
+        controller.finishTransition(transitA);
+        verify(session).close();
+    }
+
+    @Test
+    public void testReadyTrackerBasics() {
+        final TransitionController controller = new TestTransitionController(
+                mock(ActivityTaskManagerService.class));
+        controller.setFullReadyTrackingForTest(true);
+        Transition transit = createTestTransition(TRANSIT_OPEN, controller);
+        // Not ready if nothing has happened yet
+        assertFalse(transit.mReadyTracker.isReady());
+
+        Transition.ReadyCondition condition1 = new Transition.ReadyCondition("c1");
+        transit.mReadyTracker.add(condition1);
+        assertFalse(transit.mReadyTracker.isReady());
+
+        Transition.ReadyCondition condition2 = new Transition.ReadyCondition("c2");
+        transit.mReadyTracker.add(condition2);
+        assertFalse(transit.mReadyTracker.isReady());
+
+        condition2.meet();
+        assertFalse(transit.mReadyTracker.isReady());
+
+        condition1.meet();
+        assertTrue(transit.mReadyTracker.isReady());
+    }
+
+    @Test
+    public void testReadyTrackerAlternate() {
+        final TransitionController controller = new TestTransitionController(
+                mock(ActivityTaskManagerService.class));
+        controller.setFullReadyTrackingForTest(true);
+        Transition transit = createTestTransition(TRANSIT_OPEN, controller);
+
+        Transition.ReadyCondition condition1 = new Transition.ReadyCondition("c1");
+        transit.mReadyTracker.add(condition1);
+        assertFalse(transit.mReadyTracker.isReady());
+
+        condition1.meetAlternate("reason1");
+        assertTrue(transit.mReadyTracker.isReady());
+        assertEquals("reason1", condition1.mAlternate);
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 75a8dd8..085eddd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.Presubmit;
@@ -94,10 +95,14 @@
     public void testRemoveFinishingInvisibleActivityFromUnknown() {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
-        activity.finishing = true;
-        activity.setVisibleRequested(true);
-        activity.setVisibility(false);
+        assertFalse(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+        activity.makeFinishingLocked();
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+
+        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
+        assertTrue(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
+        activity.setState(ActivityRecord.State.STOPPED, "test");
+        assertFalse(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
     }
 
     @Test
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 e86fc36..eaeb804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -100,6 +100,9 @@
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerService.WindowContainerInfo;
+
+import com.google.common.truth.Expect;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -125,6 +128,9 @@
             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
             ADD_TRUSTED_DISPLAY);
 
+    @Rule
+    public Expect mExpect = Expect.create();
+
     @Test
     public void testIsRequestedOrientationMapped() {
         mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -674,64 +680,68 @@
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
-        assertThat(wct).isNull();
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null);
+        assertThat(wci).isNull();
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
         Binder cookie = new Binder("test cookie");
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isNull();
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        assertThat(wci).isNull();
 
         final ActivityRecord testActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .build();
 
-        wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isNull();
+        wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        assertThat(wci).isNull();
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
         final Binder cookie = new Binder("ginger cookie");
         final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
-        setupActivityWithLaunchCookie(cookie, launchRootTask);
+        final int uid = 123;
+        setupActivityWithLaunchCookie(cookie, launchRootTask, uid);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isEqualTo(launchRootTask);
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
+        mExpect.that(wci.getUid()).isEqualTo(uid);
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
         final Binder cookie1 = new Binder("ginger cookie");
         final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
-        setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+        final int uid1 = 123;
+        setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1);
 
         setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 456);
 
         setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 789);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
-        assertThat(wct).isEqualTo(launchRootTask1);
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1);
+        mExpect.that(wci.getToken()).isEqualTo(launchRootTask1);
+        mExpect.that(wci.getUid()).isEqualTo(uid1);
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
         setupActivityWithLaunchCookie(new Binder("ginger cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 123);
 
         setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 456);
 
         setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 789);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(
                 new Binder("some other cookie"));
-        assertThat(wct).isNull();
+        assertThat(wci).isNull();
     }
 
     @Test
@@ -778,17 +788,18 @@
     }
 
     @Test
-    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() {
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
         Task task = createTask(mDefaultDisplay);
         ActivityRecord activityRecord = createActivityRecord(task);
-        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
-                activityRecord.mLaunchCookie);
+        ContentRecordingSession session =
+                ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);
 
         wmInternal.setContentRecordingSession(session);
 
-        assertThat(session.getTokenToRecord()).isEqualTo(
-                task.mRemoteToken.toWindowContainerToken().asBinder());
+        mExpect.that(session.getTokenToRecord())
+                .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder());
+        mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());
     }
 
     @Test
@@ -1010,12 +1021,12 @@
         }
     }
 
-    private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+    private void setupActivityWithLaunchCookie(
+            IBinder launchCookie, WindowContainerToken wct, int uid) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
-        final ActivityRecord testActivity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .build();
+        final ActivityRecord testActivity =
+                new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();
         testActivity.mLaunchCookie = launchCookie;
         testActivity.getTask().mRemoteToken = remoteToken;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7168670..cd3fef6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -40,6 +40,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 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.testing.Assert.assertThrows;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
@@ -58,6 +59,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
@@ -77,11 +79,13 @@
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
+import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
+import android.window.TaskFragmentOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -579,6 +583,87 @@
     }
 
     @Test
+    public void testTaskFragmentHiddenAndFocusableChanges() {
+        removeGlobalMinSizeRestriction();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        final TaskFragmentOrganizer organizer =
+                createTaskFragmentOrganizer(t, true /* isSystemOrganizer */);
+
+        final IBinder token = new Binder();
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(rootTask)
+                .setFragmentToken(token)
+                .setOrganizer(organizer)
+                .createActivityCount(1)
+                .build();
+
+        // Should be visible and focusable initially.
+        assertTrue(rootTask.shouldBeVisible(null));
+        assertTrue(taskFragment.shouldBeVisible(null));
+        assertTrue(taskFragment.isFocusable());
+        assertTrue(taskFragment.isTopActivityFocusable());
+
+        // Apply transaction to the TaskFragment hidden and not focusable.
+        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+        mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+                t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+                false /* shouldApplyIndependently */);
+
+        // Should be not visible and not focusable after the transaction.
+        assertFalse(taskFragment.shouldBeVisible(null));
+        assertFalse(taskFragment.isFocusable());
+        assertFalse(taskFragment.isTopActivityFocusable());
+
+        // Apply transaction to the TaskFragment not hidden and focusable.
+        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+        mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+                t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+                false /* shouldApplyIndependently */);
+
+        // Should be visible and focusable after the transaction.
+        assertTrue(taskFragment.shouldBeVisible(null));
+        assertTrue(taskFragment.isFocusable());
+        assertTrue(taskFragment.isTopActivityFocusable());
+    }
+
+    @Test
+    public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() {
+        removeGlobalMinSizeRestriction();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        final TaskFragmentOrganizer organizer =
+                createTaskFragmentOrganizer(t, false /* isSystemOrganizer */);
+
+        final IBinder token = new Binder();
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(rootTask)
+                .setFragmentToken(token)
+                .setOrganizer(organizer)
+                .createActivityCount(1)
+                .build();
+
+        assertTrue(rootTask.shouldBeVisible(null));
+        assertTrue(taskFragment.shouldBeVisible(null));
+
+        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+
+        // Non-system organizers are not allow to update the hidden and focusable states.
+        assertThrows(SecurityException.class, () ->
+                mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+                        t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+                        false /* shouldApplyIndependently */)
+        );
+    }
+
+    @Test
     public void testContainerTranslucentChanges() {
         removeGlobalMinSizeRestriction();
         final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
@@ -1561,6 +1646,28 @@
         verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
     }
 
+    @Test
+    public void testSetAlwaysOnTop() {
+        final Task rootTask = new TaskBuilder(mSupervisor)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        testSetAlwaysOnTop(rootTask);
+
+        final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
+        displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        testSetAlwaysOnTop(displayArea);
+    }
+
+    private void testSetAlwaysOnTop(WindowContainer wc) {
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), true);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+        assertTrue(wc.isAlwaysOnTop());
+
+        t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), false);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+        assertFalse(wc.isAlwaysOnTop());
+    }
+
     private ActivityRecord createActivityRecordAndDispatchPendingEvents(Task task) {
         final ActivityRecord record = createActivityRecord(task);
         // Flush EVENT_APPEARED.
@@ -1600,4 +1707,20 @@
             assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
         }
     }
+
+    @NonNull
+    private TaskFragmentOrganizer createTaskFragmentOrganizer(
+            @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer organizerInterface =
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+        mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+                .registerOrganizerInternal(
+                        ITaskFragmentOrganizer.Stub.asInterface(
+                                organizer.getOrganizerToken().asBinder()),
+                        isSystemOrganizer);
+        t.setTaskFragmentOrganizer(organizerInterface);
+
+        return organizer;
+    }
 }
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 9146889..e0ed642 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1184,6 +1184,7 @@
         private boolean mCreateTask = false;
         private Task mParentTask;
         private int mActivityFlags;
+        private int mActivityTheme;
         private int mLaunchMode;
         private int mResizeMode = RESIZE_MODE_RESIZEABLE;
         private float mMaxAspectRatio;
@@ -1232,6 +1233,14 @@
             return this;
         }
 
+        ActivityBuilder setActivityTheme(int theme) {
+            mActivityTheme = theme;
+            // Use the real package of test so it can get a valid context for theme.
+            mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
+                    DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+            return this;
+        }
+
         ActivityBuilder setActivityFlags(int flags) {
             mActivityFlags = flags;
             return this;
@@ -1381,6 +1390,9 @@
             if (mTargetActivity != null) {
                 aInfo.targetActivity = mTargetActivity;
             }
+            if (mActivityTheme != 0) {
+                aInfo.theme = mActivityTheme;
+            }
             aInfo.flags |= mActivityFlags;
             aInfo.launchMode = mLaunchMode;
             aInfo.resizeMode = mResizeMode;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 3ec6f42..973ab84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -448,7 +448,6 @@
         mDisplayContent.updateImeParent();
 
         // Ime should on top of the popup IME layering target window.
-        mDisplayContent.assignChildLayers(mTransaction);
         assertWindowHigher(mImeWindow, popupImeTargetWin);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
new file mode 100644
index 0000000..320d094
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import org.mockito.internal.verification.VerificationModeFactory;
+import org.mockito.internal.verification.api.VerificationData;
+import org.mockito.invocation.Invocation;
+import org.mockito.invocation.MatchableInvocation;
+import org.mockito.verification.VerificationMode;
+
+import java.util.List;
+
+/**
+ * Verifier to check that the last call of a method received the expected argument
+ */
+public class LastCallVerifier implements VerificationMode {
+
+    /**
+     * Allows comparing the expected invocation with the last invocation on the same method
+     */
+    public static LastCallVerifier lastCall() {
+        return new LastCallVerifier();
+    }
+
+    @Override
+    public void verify(VerificationData data) {
+        List<Invocation> invocations = data.getAllInvocations();
+        MatchableInvocation target = data.getTarget();
+        for (int i = invocations.size() - 1; i >= 0; i--) {
+            final Invocation invocation = invocations.get(i);
+            if (target.hasSameMethod(invocation)) {
+                if (target.matches(invocation)) {
+                    return;
+                } else {
+                    throw new LastCallMismatch(target.getInvocation(), invocation, invocations);
+                }
+            }
+        }
+        throw new RuntimeException(target + " never invoked");
+    }
+
+    @Override
+    public VerificationMode description(String description) {
+        return VerificationModeFactory.description(this, description);
+    }
+
+    static class LastCallMismatch extends RuntimeException {
+        LastCallMismatch(
+                Invocation expected, Invocation received, List<Invocation> allInvocations) {
+            super("Expected invocation " + expected + " but received " + received
+                    + " as the last invocation.\nAll registered invocations:\n" + allInvocations);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
new file mode 100644
index 0000000..1a66970a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
@@ -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.server.wm.utils;
+
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.wm.AnimationAdapter;
+import com.android.server.wm.SurfaceAnimator;
+
+import java.io.PrintWriter;
+
+/**
+ * An empty animation adapter which just executes the finish callback
+ */
+public class MockAnimationAdapter implements AnimationAdapter {
+
+    @Override
+    public boolean getShowWallpaper() {
+        return false;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        // As the animation won't run, finish it immediately
+        finishCallback.onAnimationFinished(0, null);
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {}
+
+    @Override
+    public long getDurationHint() {
+        return 0;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return 0;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {}
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto) {}
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
new file mode 100644
index 0000000..31418d6
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
@@ -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.server.usage;
+
+import android.os.Looper;
+import android.os.Trace;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.ServiceThread;
+
+/**
+ * Shared singleton default priority thread for usage stats message handling.
+ */
+public class UsageStatsHandlerThread extends ServiceThread {
+    private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
+    private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
+
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static UsageStatsHandlerThread sInstance;
+
+    private UsageStatsHandlerThread() {
+        super("android.usagestats", android.os.Process.THREAD_PRIORITY_DEFAULT,
+                /* allowIo= */ true);
+    }
+
+    @GuardedBy("sLock")
+    private static void ensureThreadLocked() {
+        if (sInstance != null) {
+            return;
+        }
+
+        sInstance = new UsageStatsHandlerThread();
+        sInstance.start();
+        final Looper looper = sInstance.getLooper();
+        looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+        looper.setSlowLogThresholdMs(
+                SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+    }
+
+    /**
+     * Obtain a singleton instance of the UsageStatsHandlerThread.
+     */
+    public static UsageStatsHandlerThread get() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f3bf026..e413663 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -53,6 +53,7 @@
 import android.app.usage.BroadcastResponseStatsList;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventStats;
+import android.app.usage.Flags;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
@@ -200,6 +201,8 @@
     static final int MSG_ON_START = 7;
     static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
     static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
+    static final int MSG_UID_REMOVED = 10;
+    static final int MSG_USER_STARTED = 11;
 
     private final Object mLock = new Object();
     private Handler mHandler;
@@ -332,7 +335,7 @@
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
         mPackageManager = getContext().getPackageManager();
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mHandler = new H(BackgroundThread.get().getLooper());
+        mHandler = getUsageEventProcessingHandler();
         mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback);
 
         mAppStandby = mInjector.getAppStandbyController(getContext());
@@ -379,10 +382,11 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
-                null, mHandler);
+                null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
 
         getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
-                new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+                new IntentFilter(ACTION_UID_REMOVED), null,
+                /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -470,6 +474,14 @@
         }
     }
 
+    private Handler getUsageEventProcessingHandler() {
+        if (Flags.useDedicatedHandlerThread()) {
+            return new H(UsageStatsHandlerThread.get().getLooper());
+        } else {
+            return new H(BackgroundThread.get().getLooper());
+        }
+    }
+
     private void onUserUnlocked(int userId) {
         // fetch the installed packages outside the lock so it doesn't block package manager.
         final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
@@ -614,11 +626,10 @@
             if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 if (userId >= 0) {
                     mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
-                    mResponseStatsTracker.onUserRemoved(userId);
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >= 0) {
-                    mAppStandby.postCheckIdleStates(userId);
+                    mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget();
                 }
             }
         }
@@ -632,9 +643,7 @@
                 return;
             }
 
-            synchronized (mLock) {
-                mResponseStatsTracker.onUidRemoved(uid);
-            }
+            mHandler.obtainMessage(MSG_UID_REMOVED, uid, 0).sendToTarget();
         }
     }
 
@@ -1301,6 +1310,8 @@
             mPendingLaunchTimeChangePackages.remove(userId);
         }
         mAppStandby.onUserRemoved(userId);
+        mResponseStatsTracker.onUserRemoved(userId);
+
         // Cancel any scheduled jobs for this user since the user is being removed.
         UsageStatsIdleService.cancelPruneJob(getContext(), userId);
         UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId);
@@ -1554,8 +1565,7 @@
         synchronized (mLaunchTimeAlarmQueues) {
             LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
             if (alarmQueue == null) {
-                alarmQueue = new LaunchTimeAlarmQueue(
-                    userId, getContext(), BackgroundThread.get().getLooper());
+                alarmQueue = new LaunchTimeAlarmQueue(userId, getContext(), mHandler.getLooper());
                 mLaunchTimeAlarmQueues.put(userId, alarmQueue);
             }
 
@@ -2037,6 +2047,12 @@
                 case MSG_REMOVE_USER:
                     onUserRemoved(msg.arg1);
                     break;
+                case MSG_UID_REMOVED:
+                    mResponseStatsTracker.onUidRemoved(msg.arg1);
+                    break;
+                case MSG_USER_STARTED:
+                    mAppStandby.postCheckIdleStates(msg.arg1);
+                    break;
                 case MSG_PACKAGE_REMOVED:
                     onPackageRemoved(msg.arg1, (String) msg.obj);
                     break;
@@ -2125,7 +2141,8 @@
 
         private boolean canReportUsageStats() {
             if (isCallingUidSystem()) {
-                return true; // System UID can always report UsageStats
+                // System UID can always report UsageStats
+                return true;
             }
 
             return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
@@ -2621,9 +2638,12 @@
                 return;
             }
 
-            if (!canReportUsageStats()) {
-                throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS"
-                        + " permission are allowed to call reportChooserSelection");
+            if (Flags.reportUsageStatsPermission()) {
+                if (!canReportUsageStats()) {
+                    throw new SecurityException(
+                        "Only the system or holders of the REPORT_USAGE_STATS"
+                            + " permission are allowed to call reportChooserSelection");
+                }
             }
 
             // Verify if this package exists before reporting an event for it.
@@ -2643,9 +2663,17 @@
         @Override
         public void reportUserInteraction(String packageName, int userId) {
             Objects.requireNonNull(packageName);
-            if (!canReportUsageStats()) {
-                throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS"
-                        + " permission are allowed to call reportUserInteraction");
+            if (Flags.reportUsageStatsPermission()) {
+                if (!canReportUsageStats()) {
+                    throw new SecurityException(
+                        "Only the system or holders of the REPORT_USAGE_STATS"
+                            + " permission are allowed to call reportUserInteraction");
+                }
+            } else {
+                if (!isCallingUidSystem()) {
+                    throw new SecurityException("Only system is allowed to call"
+                            + " reportUserInteraction");
+                }
             }
 
             final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 5d2f27d..35e2fcf 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -37,8 +37,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.hardware.usb.IUsbManager;
 import android.hardware.usb.IDisplayPortAltModeInfoListener;
+import android.hardware.usb.IUsbManager;
 import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
@@ -46,7 +46,6 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.DisplayPortAltModeInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -1215,6 +1214,20 @@
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
                             "", 0);
                 }
+            } else if ("enable-usb-data".equals(args[0]) && args.length == 3) {
+                final String portId = args[1];
+                final boolean enable = Boolean.parseBoolean(args[2]);
+
+                if (mPortManager != null) {
+                    for (UsbPort p : mPortManager.getPorts()) {
+                        if (p.getId().equals(portId)) {
+                            int res = p.enableUsbData(enable);
+                            Slog.i(TAG, "enableUsbData " + portId + " status " + res);
+                            break;
+                        }
+                    }
+                }
+
             } else if ("ports".equals(args[0]) && args.length == 1) {
                 if (mPortManager != null) {
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
@@ -1293,6 +1306,11 @@
                 pw.println("reset-displayport-status can also be used in order to set");
                 pw.println("the DisplayPortInfo to default values.");
                 pw.println();
+                pw.println("Example enableUsbData");
+                pw.println("This dumpsys command functions for both simulated and real ports.");
+                pw.println("  dumpsys usb enable-usb-data \"matrix\" true");
+                pw.println("  dumpsys usb enable-usb-data \"matrix\" false");
+                pw.println();
                 pw.println("Example USB device descriptors:");
                 pw.println("  dumpsys usb dump-descriptors -dump-short");
                 pw.println("  dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 42b08e3..f6c6a64 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -19,7 +19,10 @@
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.LOG_COMPAT_CHANGE;
 import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
+import static android.Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 import static android.Manifest.permission.RECORD_AUDIO;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -27,6 +30,8 @@
 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
 import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
 import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_COPY_AUDIO_DATA_FAILURE;
+import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED;
+import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION;
 
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
@@ -73,6 +78,8 @@
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordDetector;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.HotwordTrainingData;
+import android.service.voice.HotwordTrainingDataLimitEnforcer;
 import android.service.voice.IDspHotwordDetectionCallback;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.VisualQueryDetectionServiceFailure;
@@ -125,6 +132,9 @@
     private static final String HOTWORD_DETECTION_OP_MESSAGE =
             "Providing hotword detection result to VoiceInteractionService";
 
+    private static final String HOTWORD_TRAINING_DATA_OP_MESSAGE =
+            "Providing hotword training data to VoiceInteractionService";
+
     // The error codes are used for onHotwordDetectionServiceFailure callback.
     // Define these due to lines longer than 100 characters.
     static final int ONDETECTED_GOT_SECURITY_EXCEPTION =
@@ -510,6 +520,25 @@
                                 }
 
                                 @Override
+                                public void onTrainingData(HotwordTrainingData data)
+                                        throws RemoteException {
+                                    sendTrainingData(new TrainingDataEgressCallback() {
+                                        @Override
+                                        public void onHotwordDetectionServiceFailure(
+                                                HotwordDetectionServiceFailure failure)
+                                                throws RemoteException {
+                                            callback.onHotwordDetectionServiceFailure(failure);
+                                        }
+
+                                        @Override
+                                        public void onTrainingData(HotwordTrainingData data)
+                                                throws RemoteException {
+                                            callback.onTrainingData(data);
+                                        }
+                                    }, data);
+                                }
+
+                                @Override
                                 public void onDetected(HotwordDetectedResult triggerResult)
                                         throws RemoteException {
                                     synchronized (mLock) {
@@ -591,6 +620,82 @@
                 mVoiceInteractionServiceUid);
     }
 
+    /** Used to send training data.
+     *
+     * @hide
+     */
+    interface TrainingDataEgressCallback {
+        /** Called to send training data */
+        void onTrainingData(HotwordTrainingData trainingData) throws RemoteException;
+
+        /** Called to inform failure to send training data. */
+        void onHotwordDetectionServiceFailure(HotwordDetectionServiceFailure failure) throws
+                RemoteException;
+
+    }
+
+    /** Default implementation to send training data from {@link HotwordDetectionService}
+     *  to {@link HotwordDetector}.
+     *
+     * <p> Verifies RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA permission has been
+     * granted and training data egress is within daily limit.
+     *
+     * @param callback used to send training data or inform of failures to send training data.
+     * @param data training data to egress.
+     *
+     * @hide
+     */
+    void sendTrainingData(
+            TrainingDataEgressCallback callback, HotwordTrainingData data) throws RemoteException {
+        Slog.d(TAG, "onTrainingData()");
+
+        // Check training data permission is granted.
+        try {
+            enforcePermissionForTrainingDataDelivery();
+        } catch (SecurityException e) {
+            Slog.w(TAG, "Ignoring training data due to a SecurityException", e);
+            try {
+                callback.onHotwordDetectionServiceFailure(
+                        new HotwordDetectionServiceFailure(
+                                ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION,
+                                "Security exception occurred"
+                                        + "in #onTrainingData method."));
+            } catch (RemoteException e1) {
+                notifyOnDetectorRemoteException();
+                throw e1;
+            }
+            return;
+        }
+
+        // Check whether within daily egress limit.
+        boolean withinEgressLimit = HotwordTrainingDataLimitEnforcer.getInstance(mContext)
+                                                                    .incrementEgressCount();
+        if (!withinEgressLimit) {
+            Slog.d(TAG, "Ignoring training data as exceeded egress limit.");
+            try {
+                callback.onHotwordDetectionServiceFailure(
+                        new HotwordDetectionServiceFailure(
+                                ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED,
+                                "Training data egress limit exceeded."));
+            } catch (RemoteException e) {
+                notifyOnDetectorRemoteException();
+                throw e;
+            }
+            return;
+        }
+
+        try {
+            Slog.i(TAG, "Egressing training data from hotword trusted process.");
+            if (mDebugHotwordLogging) {
+                Slog.d(TAG, "Egressing hotword training data " + data);
+            }
+            callback.onTrainingData(data);
+        } catch (RemoteException e) {
+            notifyOnDetectorRemoteException();
+            throw e;
+        }
+    }
+
     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
         synchronized (mLock) {
             if (mInitialized || mDestroyed) {
@@ -753,11 +858,21 @@
                                 "Failed to obtain permission RECORD_AUDIO for identity "
                                         + mVoiceInteractorIdentity);
                     }
-                    mAppOpsManager.noteOpNoThrow(
-                            AppOpsPolicy.getVoiceActivationOp(),
-                            mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                            mVoiceInteractorIdentity.attributionTag,
-                                    HOTWORD_DETECTION_OP_MESSAGE);
+                    int opMode = mAppOpsManager.unsafeCheckOpNoThrow(
+                            AppOpsManager.opToPublicName(AppOpsPolicy.getVoiceActivationOp()),
+                            mVoiceInteractorIdentity.uid,
+                            mVoiceInteractorIdentity.packageName);
+                    if (opMode == MODE_DEFAULT || opMode == MODE_ALLOWED) {
+                        mAppOpsManager.noteOpNoThrow(
+                                AppOpsPolicy.getVoiceActivationOp(),
+                                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                                mVoiceInteractorIdentity.attributionTag,
+                                HOTWORD_DETECTION_OP_MESSAGE);
+                    } else {
+                        throw new SecurityException(
+                                "The app op OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is denied for "
+                                        + "identity" + mVoiceInteractorIdentity);
+                    }
                 } else {
                     enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
                             RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE);
@@ -769,6 +884,27 @@
     }
 
     /**
+     * Enforces permission for training data delivery.
+     *
+     * <p> Throws a {@link SecurityException} if training data egress permission is not granted.
+     */
+    void enforcePermissionForTrainingDataDelivery() {
+        Binder.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
+                        RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                        HOTWORD_TRAINING_DATA_OP_MESSAGE);
+
+                mAppOpsManager.noteOpNoThrow(
+                        AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                        mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                        mVoiceInteractorIdentity.attributionTag,
+                        HOTWORD_TRAINING_DATA_OP_MESSAGE);
+            }
+        });
+    }
+
+    /**
      * Throws a {@link SecurityException} if the given identity has no permission to receive data.
      *
      * @param context    A {@link Context}, used for permission checks.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 9a4fbdc..6418f3e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -42,6 +42,7 @@
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordDetector;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.HotwordTrainingData;
 import android.service.voice.IDspHotwordDetectionCallback;
 import android.util.Slog;
 
@@ -229,6 +230,23 @@
                     }
                 }
             }
+
+            @Override
+            public void onTrainingData(HotwordTrainingData data) throws RemoteException {
+                sendTrainingData(new TrainingDataEgressCallback() {
+                    @Override
+                    public void onHotwordDetectionServiceFailure(
+                            HotwordDetectionServiceFailure failure) throws RemoteException {
+                        externalCallback.onHotwordDetectionServiceFailure(failure);
+                    }
+
+                    @Override
+                    public void onTrainingData(HotwordTrainingData data)
+                            throws RemoteException {
+                        externalCallback.onTrainingData(data);
+                    }
+                }, data);
+            }
         };
 
         mValidatingDspTrigger = true;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 0a70a5f..b098e82 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -214,7 +214,6 @@
                 new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
                         bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY);
 
-
         mLastRestartInstant = Instant.now();
 
         if (mReStartPeriodSeconds <= 0) {
@@ -918,7 +917,8 @@
             session = new SoftwareTrustedHotwordDetectorSession(
                     mRemoteHotwordDetectionService, mLock, mContext, token, callback,
                     mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
-                    mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
+                    mScheduledExecutorService, mDebugHotwordLogging,
+                    mRemoteExceptionListener);
         }
         mHotwordRecognitionCallback = callback;
         mDetectorSessions.put(detectorType, session);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index f06c997..2e23eff 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -40,6 +40,7 @@
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordDetector;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.HotwordTrainingData;
 import android.service.voice.IDspHotwordDetectionCallback;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
@@ -195,6 +196,21 @@
                         mVoiceInteractionServiceUid);
                 // onRejected isn't allowed here, and we are not expecting it.
             }
+
+            public void onTrainingData(HotwordTrainingData data) throws RemoteException {
+                sendTrainingData(new TrainingDataEgressCallback() {
+                    @Override
+                    public void onHotwordDetectionServiceFailure(
+                            HotwordDetectionServiceFailure failure) throws RemoteException {
+                        mSoftwareCallback.onHotwordDetectionServiceFailure(failure);
+                    }
+
+                    @Override
+                    public void onTrainingData(HotwordTrainingData data) throws RemoteException {
+                        mSoftwareCallback.onTrainingData(data);
+                    }
+                }, data);
+            }
         };
 
         mRemoteDetectionService.run(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 138e575..a584fc9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -58,6 +58,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -67,6 +68,7 @@
 import android.os.ShellCallback;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.permission.flags.Flags;
 import android.provider.Settings;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -1286,6 +1288,17 @@
             }
         }
 
+        // Enforce permissions that are flag controlled. The flag value decides if the permission
+        // should be enforced.
+        private void initAndVerifyDetector_enforcePermissionWithFlags() {
+            PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class);
+            if (Flags.voiceActivationPermissionApis()) {
+                enforcer.enforcePermission(
+                        android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                        getCallingPid(), getCallingUid());
+            }
+        }
+
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
         @Override
         public void initAndVerifyDetector(
@@ -1295,7 +1308,13 @@
                 @NonNull IBinder token,
                 IHotwordRecognitionStatusCallback callback,
                 int detectorType) {
+            // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+            // {@link #initAndVerifyDetector(Identity,  PersistableBundle, ShareMemory, IBinder,
+            // IHotwordRecognitionStatusCallback, int)}
+            // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully
+            // launched.
             super.initAndVerifyDetector_enforcePermission();
+            initAndVerifyDetector_enforcePermissionWithFlags();
 
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
@@ -1522,7 +1541,31 @@
                 }
             }
         }
-        //----------------- Model management APIs --------------------------------//
+
+        @Override
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT)
+        public void resetHotwordTrainingDataEgressCountForTest() {
+            super.resetHotwordTrainingDataEgressCountForTest_enforcePermission();
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService();
+
+                if (mImpl == null) {
+                    Slog.w(TAG, "resetHotwordTrainingDataEgressCountForTest without running"
+                            + " voice interaction service");
+                    return;
+                }
+                final long caller = Binder.clearCallingIdentity();
+                try {
+                    mImpl.resetHotwordTrainingDataEgressCountForTest();
+                } finally {
+                    Binder.restoreCallingIdentity(caller);
+                }
+
+            }
+        }
+
+      //----------------- Model management APIs --------------------------------//
 
         @Override
         public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 6ba77da..3c4b58f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -60,6 +60,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.service.voice.HotwordDetector;
+import android.service.voice.HotwordTrainingDataLimitEnforcer;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
 import android.service.voice.IVoiceInteractionService;
@@ -72,6 +73,7 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
@@ -991,6 +993,12 @@
         }
     }
 
+    @VisibleForTesting
+    void resetHotwordTrainingDataEgressCountForTest() {
+        HotwordTrainingDataLimitEnforcer.getInstance(mContext.getApplicationContext())
+                        .resetTrainingDataEgressCount();
+    }
+
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
deleted file mode 100644
index e172090..0000000
--- a/startop/view_compiler/Android.bp
+++ /dev/null
@@ -1,115 +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 {
-    // 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"],
-}
-
-cc_defaults {
-    name: "viewcompiler_defaults",
-    header_libs: [
-        "libbase_headers",
-    ],
-    shared_libs: [
-        "libbase",
-        "slicer",
-    ],
-    static_libs: [
-        "libcutils",
-        "libtinyxml2",
-        "liblog",
-        "libutils",
-        "libziparchive",
-        "libz",
-    ],
-    cpp_std: "gnu++2b",
-    target: {
-        android: {
-            shared_libs: [
-                "libandroidfw",
-            ],
-        },
-        host: {
-            static_libs: [
-                "libandroidfw",
-            ],
-        },
-    },
-}
-
-cc_library_static {
-    name: "libviewcompiler",
-    defaults: ["viewcompiler_defaults"],
-    srcs: [
-        "apk_layout_compiler.cc",
-        "dex_builder.cc",
-        "dex_layout_compiler.cc",
-        "java_lang_builder.cc",
-        "tinyxml_layout_parser.cc",
-        "util.cc",
-        "layout_validation.cc",
-    ],
-    host_supported: true,
-}
-
-cc_binary {
-    name: "viewcompiler",
-    defaults: ["viewcompiler_defaults"],
-    srcs: [
-        "main.cc",
-    ],
-    static_libs: [
-        "libgflags",
-        "libviewcompiler",
-    ],
-    host_supported: true,
-}
-
-cc_test_host {
-    name: "view-compiler-tests",
-    defaults: ["viewcompiler_defaults"],
-    srcs: [
-        "layout_validation_test.cc",
-        "util_test.cc",
-    ],
-    static_libs: [
-        "libviewcompiler",
-    ],
-}
-
-cc_binary_host {
-    name: "dex_testcase_generator",
-    defaults: ["viewcompiler_defaults"],
-    srcs: ["dex_testcase_generator.cc"],
-    static_libs: [
-        "libviewcompiler",
-    ],
-}
-
-genrule {
-    name: "generate_dex_testcases",
-    tools: [":dex_testcase_generator"],
-    cmd: "$(location :dex_testcase_generator) $(genDir)",
-    out: [
-        "simple.dex",
-        "trivial.dex",
-    ],
-}
diff --git a/startop/view_compiler/OWNERS b/startop/view_compiler/OWNERS
deleted file mode 100644
index e5aead9..0000000
--- a/startop/view_compiler/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-eholk@google.com
-mathieuc@google.com
diff --git a/startop/view_compiler/README.md b/startop/view_compiler/README.md
deleted file mode 100644
index f8da02b..0000000
--- a/startop/view_compiler/README.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# View Compiler
-
-This directory contains an experimental compiler for layout files.
-
-It will take a layout XML file and produce a CompiledLayout.java file with a
-specialized layout inflation function.
-
-To use it, let's assume you had a layout in `my_layout.xml` and your app was in
-the Java language package `com.example.myapp`. Run the following command:
-
-    viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java
-
-This will produce a `CompiledView.java`, which can then be compiled into your
-Android app. Then to use it, in places where you would have inflated
-`R.layouts.my_layout`, instead call `CompiledView.inflate`.
-
-Precompiling views like this generally improves the time needed to inflate them.
-
-This tool is still in its early stages and has a number of limitations.
-* Currently only one layout can be compiled at a time.
-* `merge` and `include` nodes are not supported.
-* View compilation is a manual process that requires code changes in the
-  application.
-* This only works for apps that do not use a custom layout inflater.
-* Other limitations yet to be discovered.
-
-## DexBuilder Tests
-
-The DexBuilder has several low-level end to end tests to verify generated DEX
-code validates, runs, and has the correct behavior. There are, unfortunately, a
-number of pieces that must be added to generate new tests. Here are the
-components:
-
-* `dex_testcase_generator` - Written in C++ using `DexBuilder`. This runs as a
-  build step produce the DEX files that will be tested on device. See the
-  `genrule` named `generate_dex_testcases` in `Android.bp`. These files are then
-  copied over to the device by TradeFed when running tests.
-* `DexBuilderTest` - This is a Java Language test harness that loads the
-  generated DEX files and exercises methods in the file.
-
-To add a new DEX file test, follow these steps:
-1. Modify `dex_testcase_generator` to produce the DEX file.
-2. Add the filename to the `out` list of the `generate_dex_testcases` rule in
-   `Android.bp`.
-3. Add a new `push` option to `AndroidTest.xml` to copy the DEX file to the
-   device.
-4. Modify `DexBuilderTest.java` to load and exercise the new test.
-
-In each case, you should be able to cargo-cult the existing test cases.
-
-In general, you can probably get by without adding a new generated DEX file, and
-instead add more methods to the files that are already generated. In this case,
-you can skip all of steps 2 and 3 above, and simplify steps 1 and 4.
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
deleted file mode 100644
index 5f5652c..0000000
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ /dev/null
@@ -1,179 +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.
- */
-
-#include "apk_layout_compiler.h"
-#include "dex_layout_compiler.h"
-#include "java_lang_builder.h"
-#include "layout_validation.h"
-#include "util.h"
-
-#include "androidfw/ApkAssets.h"
-#include "androidfw/AssetManager2.h"
-#include "androidfw/ResourceTypes.h"
-
-#include <iostream>
-#include <locale>
-
-#include "android-base/stringprintf.h"
-
-namespace startop {
-
-using android::ResXMLParser;
-using android::base::StringPrintf;
-
-class ResXmlVisitorAdapter {
- public:
-  ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
-
-  template <typename Visitor>
-  void Accept(Visitor* visitor) {
-    size_t depth{0};
-    do {
-      switch (parser_->next()) {
-        case ResXMLParser::START_DOCUMENT:
-          depth++;
-          visitor->VisitStartDocument();
-          break;
-        case ResXMLParser::END_DOCUMENT:
-          depth--;
-          visitor->VisitEndDocument();
-          break;
-        case ResXMLParser::START_TAG: {
-          depth++;
-          size_t name_length = 0;
-          const char16_t* name = parser_->getElementName(&name_length);
-          visitor->VisitStartTag(std::u16string{name, name_length});
-          break;
-        }
-        case ResXMLParser::END_TAG:
-          depth--;
-          visitor->VisitEndTag();
-          break;
-        default:;
-      }
-    } while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
-  }
-
- private:
-  ResXMLParser* parser_;
-};
-
-bool CanCompileLayout(ResXMLParser* parser) {
-  ResXmlVisitorAdapter adapter{parser};
-  LayoutValidationVisitor visitor;
-  adapter.Accept(&visitor);
-
-  return visitor.can_compile();
-}
-
-namespace {
-void CompileApkAssetsLayouts(const android::ApkAssetsPtr& assets, CompilationTarget target,
-                             std::ostream& target_out) {
-  android::AssetManager2 resources;
-  resources.SetApkAssets({assets});
-
-  std::string package_name;
-
-  // TODO: handle multiple packages better
-  bool first = true;
-  for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
-    CHECK(first);
-    package_name = package->GetPackageName();
-    first = false;
-  }
-
-  dex::DexBuilder dex_file;
-  dex::ClassBuilder compiled_view{
-      dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
-  std::vector<dex::MethodBuilder> methods;
-
-  assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) {
-      if (s == "layout") {
-          auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data());
-          assets->GetAssetsProvider()
-                  ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) {
-                      auto layout_path = StringPrintf("%s%.*s", path.c_str(),
-                                                      (int)layout_file.size(), layout_file.data());
-                      android::ApkAssetsCookie cookie = android::kInvalidCookie;
-                      auto asset = resources.OpenNonAsset(layout_path,
-                                                          android::Asset::ACCESS_RANDOM, &cookie);
-                      CHECK(asset);
-                      CHECK(android::kInvalidCookie != cookie);
-                      const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
-                      CHECK(nullptr != dynamic_ref_table);
-                      android::ResXMLTree xml_tree{dynamic_ref_table};
-                      xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(),
-                                     /*copy_data=*/true);
-                      android::ResXMLParser parser{xml_tree};
-                      parser.restart();
-                      if (CanCompileLayout(&parser)) {
-                          parser.restart();
-                          const std::string layout_name =
-                                  startop::util::FindLayoutNameFromFilename(layout_path);
-                          ResXmlVisitorAdapter adapter{&parser};
-                          switch (target) {
-                              case CompilationTarget::kDex: {
-                                  methods.push_back(compiled_view.CreateMethod(
-                                          layout_name,
-                                          dex::Prototype{dex::TypeDescriptor::FromClassname(
-                                                                 "android.view.View"),
-                                                         dex::TypeDescriptor::FromClassname(
-                                                                 "android.content.Context"),
-                                                         dex::TypeDescriptor::Int()}));
-                                  DexViewBuilder builder(&methods.back());
-                                  builder.Start();
-                                  LayoutCompilerVisitor visitor{&builder};
-                                  adapter.Accept(&visitor);
-                                  builder.Finish();
-                                  methods.back().Encode();
-                                  break;
-                              }
-                              case CompilationTarget::kJavaLanguage: {
-                                  JavaLangViewBuilder builder{package_name, layout_name,
-                                                              target_out};
-                                  builder.Start();
-                                  LayoutCompilerVisitor visitor{&builder};
-                                  adapter.Accept(&visitor);
-                                  builder.Finish();
-                                  break;
-                              }
-                          }
-                      }
-                  });
-      }
-  });
-
-  if (target == CompilationTarget::kDex) {
-    slicer::MemView image{dex_file.CreateImage()};
-    target_out.write(image.ptr<const char>(), image.size());
-  }
-}
-}  // namespace
-
-void CompileApkLayouts(const std::string& filename, CompilationTarget target,
-                       std::ostream& target_out) {
-  auto assets = android::ApkAssets::Load(filename);
-  CompileApkAssetsLayouts(assets, target, target_out);
-}
-
-void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
-                         std::ostream& target_out) {
-  constexpr const char* friendly_name{"viewcompiler assets"};
-  auto assets = android::ApkAssets::LoadFromFd(std::move(fd), friendly_name);
-  CompileApkAssetsLayouts(assets, target, target_out);
-}
-
-}  // namespace startop
diff --git a/startop/view_compiler/apk_layout_compiler.h b/startop/view_compiler/apk_layout_compiler.h
deleted file mode 100644
index 03bd545..0000000
--- a/startop/view_compiler/apk_layout_compiler.h
+++ /dev/null
@@ -1,35 +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.
- */
-
-#ifndef APK_LAYOUT_COMPILER_H_
-#define APK_LAYOUT_COMPILER_H_
-
-#include <string>
-
-#include "android-base/unique_fd.h"
-
-namespace startop {
-
-enum class CompilationTarget { kJavaLanguage, kDex };
-
-void CompileApkLayouts(const std::string& filename, CompilationTarget target,
-                       std::ostream& target_out);
-void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
-                         std::ostream& target_out);
-
-}  // namespace startop
-
-#endif  // APK_LAYOUT_COMPILER_H_
\ No newline at end of file
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
deleted file mode 100644
index 50cf5a50..0000000
--- a/startop/view_compiler/dex_builder.cc
+++ /dev/null
@@ -1,704 +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.
- */
-
-#include "dex_builder.h"
-
-#include <fstream>
-#include <memory>
-
-namespace startop {
-namespace dex {
-
-using std::shared_ptr;
-using std::string;
-
-using ::dex::kAccPublic;
-using Op = Instruction::Op;
-
-const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; };
-const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; };
-
-namespace {
-// From https://source.android.com/devices/tech/dalvik/dex-format#dex-file-magic
-constexpr uint8_t kDexFileMagic[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00};
-
-// Strings lengths can be 32 bits long, but encoded as LEB128 this can take up to five bytes.
-constexpr size_t kMaxEncodedStringLength{5};
-
-// Converts invoke-* to invoke-*/range
-constexpr ::dex::Opcode InvokeToInvokeRange(::dex::Opcode opcode) {
-  switch (opcode) {
-    case ::dex::Opcode::OP_INVOKE_VIRTUAL:
-      return ::dex::Opcode::OP_INVOKE_VIRTUAL_RANGE;
-    case ::dex::Opcode::OP_INVOKE_DIRECT:
-      return ::dex::Opcode::OP_INVOKE_DIRECT_RANGE;
-    case ::dex::Opcode::OP_INVOKE_STATIC:
-      return ::dex::Opcode::OP_INVOKE_STATIC_RANGE;
-    case ::dex::Opcode::OP_INVOKE_INTERFACE:
-      return ::dex::Opcode::OP_INVOKE_INTERFACE_RANGE;
-    default:
-      LOG(FATAL) << opcode << " is not a recognized invoke opcode.";
-      __builtin_unreachable();
-  }
-}
-
-std::string DotToDescriptor(const char* class_name) {
-  std::string descriptor(class_name);
-  std::replace(descriptor.begin(), descriptor.end(), '.', '/');
-  if (descriptor.length() > 0 && descriptor[0] != '[') {
-    descriptor = "L" + descriptor + ";";
-  }
-  return descriptor;
-}
-
-}  // namespace
-
-std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) {
-  switch (opcode) {
-    case Instruction::Op::kReturn:
-      out << "kReturn";
-      return out;
-    case Instruction::Op::kReturnObject:
-      out << "kReturnObject";
-      return out;
-    case Instruction::Op::kMove:
-      out << "kMove";
-      return out;
-    case Instruction::Op::kMoveObject:
-      out << "kMoveObject";
-      return out;
-    case Instruction::Op::kInvokeVirtual:
-      out << "kInvokeVirtual";
-      return out;
-    case Instruction::Op::kInvokeDirect:
-      out << "kInvokeDirect";
-      return out;
-    case Instruction::Op::kInvokeStatic:
-      out << "kInvokeStatic";
-      return out;
-    case Instruction::Op::kInvokeInterface:
-      out << "kInvokeInterface";
-      return out;
-    case Instruction::Op::kBindLabel:
-      out << "kBindLabel";
-      return out;
-    case Instruction::Op::kBranchEqz:
-      out << "kBranchEqz";
-      return out;
-    case Instruction::Op::kBranchNEqz:
-      out << "kBranchNEqz";
-      return out;
-    case Instruction::Op::kNew:
-      out << "kNew";
-      return out;
-    case Instruction::Op::kCheckCast:
-      out << "kCheckCast";
-      return out;
-    case Instruction::Op::kGetStaticField:
-      out << "kGetStaticField";
-      return out;
-    case Instruction::Op::kSetStaticField:
-      out << "kSetStaticField";
-      return out;
-    case Instruction::Op::kGetInstanceField:
-      out << "kGetInstanceField";
-      return out;
-    case Instruction::Op::kSetInstanceField:
-      out << "kSetInstanceField";
-      return out;
-  }
-}
-
-std::ostream& operator<<(std::ostream& out, const Value& value) {
-  if (value.is_register()) {
-    out << "Register(" << value.value() << ")";
-  } else if (value.is_parameter()) {
-    out << "Parameter(" << value.value() << ")";
-  } else if (value.is_immediate()) {
-    out << "Immediate(" << value.value() << ")";
-  } else if (value.is_string()) {
-    out << "String(" << value.value() << ")";
-  } else if (value.is_label()) {
-    out << "Label(" << value.value() << ")";
-  } else if (value.is_type()) {
-    out << "Type(" << value.value() << ")";
-  } else {
-    out << "UnknownValue";
-  }
-  return out;
-}
-
-void* TrackingAllocator::Allocate(size_t size) {
-  std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(size);
-  void* raw_buffer = buffer.get();
-  allocations_[raw_buffer] = std::move(buffer);
-  return raw_buffer;
-}
-
-void TrackingAllocator::Free(void* ptr) { allocations_.erase(allocations_.find(ptr)); }
-
-// Write out a DEX file that is basically:
-//
-// package dextest;
-// public class DexTest {
-//     public static int foo(String s) { return s.length(); }
-// }
-void WriteTestDexFile(const string& filename) {
-  DexBuilder dex_file;
-
-  ClassBuilder cbuilder{dex_file.MakeClass("dextest.DexTest")};
-  cbuilder.set_source_file("dextest.java");
-
-  TypeDescriptor string_type = TypeDescriptor::FromClassname("java.lang.String");
-
-  MethodBuilder method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), string_type})};
-
-  LiveRegister result = method.AllocRegister();
-
-  MethodDeclData string_length =
-      dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()});
-
-  method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
-  method.BuildReturn(result);
-
-  method.Encode();
-
-  slicer::MemView image{dex_file.CreateImage()};
-
-  std::ofstream out_file(filename);
-  out_file.write(image.ptr<const char>(), image.size());
-}
-
-TypeDescriptor TypeDescriptor::FromClassname(const std::string& name) {
-  return TypeDescriptor{DotToDescriptor(name.c_str())};
-}
-
-DexBuilder::DexBuilder() : dex_file_{std::make_shared<ir::DexFile>()} {
-  dex_file_->magic = slicer::MemView{kDexFileMagic, sizeof(kDexFileMagic)};
-}
-
-slicer::MemView DexBuilder::CreateImage() {
-  ::dex::Writer writer(dex_file_);
-  size_t image_size{0};
-  ::dex::u1* image = writer.CreateImage(&allocator_, &image_size);
-  return slicer::MemView{image, image_size};
-}
-
-ir::String* DexBuilder::GetOrAddString(const std::string& string) {
-  ir::String*& entry = strings_[string];
-
-  if (entry == nullptr) {
-    // Need to encode the length and then write out the bytes, including 1 byte for null terminator
-    auto buffer = std::make_unique<uint8_t[]>(string.size() + kMaxEncodedStringLength + 1);
-    uint8_t* string_data_start = ::dex::WriteULeb128(buffer.get(), string.size());
-
-    size_t header_length =
-        reinterpret_cast<uintptr_t>(string_data_start) - reinterpret_cast<uintptr_t>(buffer.get());
-
-    auto end = std::copy(string.begin(), string.end(), string_data_start);
-    *end = '\0';
-
-    entry = Alloc<ir::String>();
-    // +1 for null terminator
-    entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1};
-    ::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex();
-    dex_file_->strings_map[new_index] = entry;
-    entry->orig_index = new_index;
-    string_data_.push_back(std::move(buffer));
-  }
-  return entry;
-}
-
-ClassBuilder DexBuilder::MakeClass(const std::string& name) {
-  auto* class_def = Alloc<ir::Class>();
-  ir::Type* type_def = GetOrAddType(DotToDescriptor(name.c_str()));
-  type_def->class_def = class_def;
-
-  class_def->type = type_def;
-  class_def->super_class = GetOrAddType(DotToDescriptor("java.lang.Object"));
-  class_def->access_flags = kAccPublic;
-  return ClassBuilder{this, name, class_def};
-}
-
-ir::Type* DexBuilder::GetOrAddType(const std::string& descriptor) {
-  if (types_by_descriptor_.find(descriptor) != types_by_descriptor_.end()) {
-    return types_by_descriptor_[descriptor];
-  }
-
-  ir::Type* type = Alloc<ir::Type>();
-  type->descriptor = GetOrAddString(descriptor);
-  types_by_descriptor_[descriptor] = type;
-  type->orig_index = dex_file_->types_indexes.AllocateIndex();
-  dex_file_->types_map[type->orig_index] = type;
-  return type;
-}
-
-ir::FieldDecl* DexBuilder::GetOrAddField(TypeDescriptor parent, const std::string& name,
-                                         TypeDescriptor type) {
-  const auto key = std::make_tuple(parent, name);
-  if (field_decls_by_key_.find(key) != field_decls_by_key_.end()) {
-    return field_decls_by_key_[key];
-  }
-
-  ir::FieldDecl* field = Alloc<ir::FieldDecl>();
-  field->parent = GetOrAddType(parent);
-  field->name = GetOrAddString(name);
-  field->type = GetOrAddType(type);
-  field->orig_index = dex_file_->fields_indexes.AllocateIndex();
-  dex_file_->fields_map[field->orig_index] = field;
-  field_decls_by_key_[key] = field;
-  return field;
-}
-
-ir::Proto* Prototype::Encode(DexBuilder* dex) const {
-  auto* proto = dex->Alloc<ir::Proto>();
-  proto->shorty = dex->GetOrAddString(Shorty());
-  proto->return_type = dex->GetOrAddType(return_type_.descriptor());
-  if (param_types_.size() > 0) {
-    proto->param_types = dex->Alloc<ir::TypeList>();
-    for (const auto& param_type : param_types_) {
-      proto->param_types->types.push_back(dex->GetOrAddType(param_type.descriptor()));
-    }
-  } else {
-    proto->param_types = nullptr;
-  }
-  return proto;
-}
-
-std::string Prototype::Shorty() const {
-  std::string shorty;
-  shorty.append(return_type_.short_descriptor());
-  for (const auto& type_descriptor : param_types_) {
-    shorty.append(type_descriptor.short_descriptor());
-  }
-  return shorty;
-}
-
-const TypeDescriptor& Prototype::ArgType(size_t index) const {
-  CHECK_LT(index, param_types_.size());
-  return param_types_[index];
-}
-
-ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def)
-    : parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {}
-
-MethodBuilder ClassBuilder::CreateMethod(const std::string& name, Prototype prototype) {
-  ir::MethodDecl* decl = parent_->GetOrDeclareMethod(type_descriptor_, name, prototype).decl;
-
-  return MethodBuilder{parent_, class_, decl};
-}
-
-void ClassBuilder::set_source_file(const string& source) {
-  class_->source_file = parent_->GetOrAddString(source);
-}
-
-MethodBuilder::MethodBuilder(DexBuilder* dex, ir::Class* class_def, ir::MethodDecl* decl)
-    : dex_{dex}, class_{class_def}, decl_{decl} {}
-
-ir::EncodedMethod* MethodBuilder::Encode() {
-  auto* method = dex_->Alloc<ir::EncodedMethod>();
-  method->decl = decl_;
-
-  // TODO: make access flags configurable
-  method->access_flags = kAccPublic | ::dex::kAccStatic;
-
-  auto* code = dex_->Alloc<ir::Code>();
-  CHECK(decl_->prototype != nullptr);
-  size_t const num_args =
-      decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0;
-  code->registers = NumRegisters() + num_args + kMaxScratchRegisters;
-  code->ins_count = num_args;
-  EncodeInstructions();
-  code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size());
-  size_t const return_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
-  code->outs_count = std::max(return_count, max_args_);
-  method->code = code;
-
-  class_->direct_methods.push_back(method);
-
-  return method;
-}
-
-LiveRegister MethodBuilder::AllocRegister() {
-  // Find a free register
-  for (size_t i = 0; i < register_liveness_.size(); ++i) {
-    if (!register_liveness_[i]) {
-      register_liveness_[i] = true;
-      return LiveRegister{&register_liveness_, i};
-    }
-  }
-
-  // If we get here, all the registers are in use, so we have to allocate a new
-  // one.
-  register_liveness_.push_back(true);
-  return LiveRegister{&register_liveness_, register_liveness_.size() - 1};
-}
-
-Value MethodBuilder::MakeLabel() {
-  labels_.push_back({});
-  return Value::Label(labels_.size() - 1);
-}
-
-void MethodBuilder::AddInstruction(Instruction instruction) {
-  instructions_.push_back(instruction);
-}
-
-void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); }
-
-void MethodBuilder::BuildReturn(Value src, bool is_object) {
-  AddInstruction(Instruction::OpWithArgs(
-      is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src));
-}
-
-void MethodBuilder::BuildConst4(Value target, int value) {
-  CHECK_LT(value, 16);
-  AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value)));
-}
-
-void MethodBuilder::BuildConstString(Value target, const std::string& value) {
-  const ir::String* const dex_string = dex_->GetOrAddString(value);
-  AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index)));
-}
-
-void MethodBuilder::EncodeInstructions() {
-  buffer_.clear();
-  for (const auto& instruction : instructions_) {
-    EncodeInstruction(instruction);
-  }
-}
-
-void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
-  switch (instruction.opcode()) {
-    case Instruction::Op::kReturn:
-      return EncodeReturn(instruction, ::dex::Opcode::OP_RETURN);
-    case Instruction::Op::kReturnObject:
-      return EncodeReturn(instruction, ::dex::Opcode::OP_RETURN_OBJECT);
-    case Instruction::Op::kMove:
-    case Instruction::Op::kMoveObject:
-      return EncodeMove(instruction);
-    case Instruction::Op::kInvokeVirtual:
-      return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_VIRTUAL);
-    case Instruction::Op::kInvokeDirect:
-      return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_DIRECT);
-    case Instruction::Op::kInvokeStatic:
-      return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_STATIC);
-    case Instruction::Op::kInvokeInterface:
-      return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_INTERFACE);
-    case Instruction::Op::kBindLabel:
-      return BindLabel(instruction.args()[0]);
-    case Instruction::Op::kBranchEqz:
-      return EncodeBranch(::dex::Opcode::OP_IF_EQZ, instruction);
-    case Instruction::Op::kBranchNEqz:
-      return EncodeBranch(::dex::Opcode::OP_IF_NEZ, instruction);
-    case Instruction::Op::kNew:
-      return EncodeNew(instruction);
-    case Instruction::Op::kCheckCast:
-      return EncodeCast(instruction);
-    case Instruction::Op::kGetStaticField:
-    case Instruction::Op::kSetStaticField:
-    case Instruction::Op::kGetInstanceField:
-    case Instruction::Op::kSetInstanceField:
-      return EncodeFieldOp(instruction);
-  }
-}
-
-void MethodBuilder::EncodeReturn(const Instruction& instruction, ::dex::Opcode opcode) {
-  CHECK(!instruction.dest().has_value());
-  if (instruction.args().size() == 0) {
-    Encode10x(::dex::Opcode::OP_RETURN_VOID);
-  } else {
-    CHECK_EQ(1, instruction.args().size());
-    size_t source = RegisterValue(instruction.args()[0]);
-    Encode11x(opcode, source);
-  }
-}
-
-void MethodBuilder::EncodeMove(const Instruction& instruction) {
-  CHECK(Instruction::Op::kMove == instruction.opcode() ||
-        Instruction::Op::kMoveObject == instruction.opcode());
-  CHECK(instruction.dest().has_value());
-  CHECK(instruction.dest()->is_variable());
-  CHECK_EQ(1, instruction.args().size());
-
-  const Value& source = instruction.args()[0];
-
-  if (source.is_immediate()) {
-    // TODO: support more registers
-    CHECK_LT(RegisterValue(*instruction.dest()), 16);
-    Encode11n(::dex::Opcode::OP_CONST_4, RegisterValue(*instruction.dest()), source.value());
-  } else if (source.is_string()) {
-    constexpr size_t kMaxRegisters = 256;
-    CHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters);
-    CHECK_LT(source.value(), 65536);  // make sure we don't need a jumbo string
-    Encode21c(::dex::Opcode::OP_CONST_STRING, RegisterValue(*instruction.dest()), source.value());
-  } else if (source.is_variable()) {
-    // For the moment, we only use this when we need to reshuffle registers for
-    // an invoke instruction, meaning we are too big for the 4-bit version.
-    // We'll err on the side of caution and always generate the 16-bit form of
-    // the instruction.
-    auto opcode = instruction.opcode() == Instruction::Op::kMove
-                        ? ::dex::Opcode::OP_MOVE_16
-                        : ::dex::Opcode::OP_MOVE_OBJECT_16;
-    Encode32x(opcode, RegisterValue(*instruction.dest()), RegisterValue(source));
-  } else {
-    UNIMPLEMENTED(FATAL);
-  }
-}
-
-void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::dex::Opcode opcode) {
-  constexpr size_t kMaxArgs = 5;
-
-  // Currently, we only support up to 5 arguments.
-  CHECK_LE(instruction.args().size(), kMaxArgs);
-
-  uint8_t arguments[kMaxArgs]{};
-  bool has_long_args = false;
-  for (size_t i = 0; i < instruction.args().size(); ++i) {
-    CHECK(instruction.args()[i].is_variable());
-    arguments[i] = RegisterValue(instruction.args()[i]);
-    if (!IsShortRegister(arguments[i])) {
-      has_long_args = true;
-    }
-  }
-
-  if (has_long_args) {
-    // Some of the registers don't fit in the four bit short form of the invoke
-    // instruction, so we need to do an invoke/range. To do this, we need to
-    // first move all the arguments into contiguous temporary registers.
-    std::array<Value, kMaxArgs> scratch = GetScratchRegisters<kMaxArgs>();
-
-    const auto& prototype = dex_->GetPrototypeByMethodId(instruction.index_argument());
-    CHECK(prototype.has_value());
-
-    for (size_t i = 0; i < instruction.args().size(); ++i) {
-      Instruction::Op move_op;
-      if (opcode == ::dex::Opcode::OP_INVOKE_VIRTUAL ||
-          opcode == ::dex::Opcode::OP_INVOKE_DIRECT) {
-        // In this case, there is an implicit `this` argument, which is always an object.
-        if (i == 0) {
-          move_op = Instruction::Op::kMoveObject;
-        } else {
-          move_op = prototype->ArgType(i - 1).is_object() ? Instruction::Op::kMoveObject
-                                                          : Instruction::Op::kMove;
-        }
-      } else {
-        move_op = prototype->ArgType(i).is_object() ? Instruction::Op::kMoveObject
-                                                    : Instruction::Op::kMove;
-      }
-
-      EncodeMove(Instruction::OpWithArgs(move_op, scratch[i], instruction.args()[i]));
-    }
-
-    Encode3rc(InvokeToInvokeRange(opcode),
-              instruction.args().size(),
-              instruction.index_argument(),
-              RegisterValue(scratch[0]));
-  } else {
-    Encode35c(opcode,
-              instruction.args().size(),
-              instruction.index_argument(),
-              arguments[0],
-              arguments[1],
-              arguments[2],
-              arguments[3],
-              arguments[4]);
-  }
-
-  // If there is a return value, add a move-result instruction
-  if (instruction.dest().has_value()) {
-    Encode11x(instruction.result_is_object() ? ::dex::Opcode::OP_MOVE_RESULT_OBJECT
-                                             : ::dex::Opcode::OP_MOVE_RESULT,
-              RegisterValue(*instruction.dest()));
-  }
-
-  max_args_ = std::max(max_args_, instruction.args().size());
-}
-
-// Encodes a conditional branch that tests a single argument.
-void MethodBuilder::EncodeBranch(::dex::Opcode op, const Instruction& instruction) {
-  const auto& args = instruction.args();
-  const auto& test_value = args[0];
-  const auto& branch_target = args[1];
-  CHECK_EQ(2, args.size());
-  CHECK(test_value.is_variable());
-  CHECK(branch_target.is_label());
-
-  size_t instruction_offset = buffer_.size();
-  size_t field_offset = buffer_.size() + 1;
-  Encode21c(
-      op, RegisterValue(test_value), LabelValue(branch_target, instruction_offset, field_offset));
-}
-
-void MethodBuilder::EncodeNew(const Instruction& instruction) {
-  CHECK_EQ(Instruction::Op::kNew, instruction.opcode());
-  CHECK(instruction.dest().has_value());
-  CHECK(instruction.dest()->is_variable());
-  CHECK_EQ(1, instruction.args().size());
-
-  const Value& type = instruction.args()[0];
-  CHECK_LT(RegisterValue(*instruction.dest()), 256);
-  CHECK(type.is_type());
-  Encode21c(::dex::Opcode::OP_NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value());
-}
-
-void MethodBuilder::EncodeCast(const Instruction& instruction) {
-  CHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode());
-  CHECK(instruction.dest().has_value());
-  CHECK(instruction.dest()->is_variable());
-  CHECK_EQ(1, instruction.args().size());
-
-  const Value& type = instruction.args()[0];
-  CHECK_LT(RegisterValue(*instruction.dest()), 256);
-  CHECK(type.is_type());
-  Encode21c(::dex::Opcode::OP_CHECK_CAST, RegisterValue(*instruction.dest()), type.value());
-}
-
-void MethodBuilder::EncodeFieldOp(const Instruction& instruction) {
-  const auto& args = instruction.args();
-  switch (instruction.opcode()) {
-    case Instruction::Op::kGetStaticField: {
-      CHECK(instruction.dest().has_value());
-      CHECK(instruction.dest()->is_variable());
-      CHECK_EQ(0, instruction.args().size());
-
-      Encode21c(::dex::Opcode::OP_SGET,
-                RegisterValue(*instruction.dest()),
-                instruction.index_argument());
-      break;
-    }
-    case Instruction::Op::kSetStaticField: {
-      CHECK(!instruction.dest().has_value());
-      CHECK_EQ(1, args.size());
-      CHECK(args[0].is_variable());
-
-      Encode21c(::dex::Opcode::OP_SPUT, RegisterValue(args[0]), instruction.index_argument());
-      break;
-    }
-    case Instruction::Op::kGetInstanceField: {
-      CHECK(instruction.dest().has_value());
-      CHECK(instruction.dest()->is_variable());
-      CHECK_EQ(1, instruction.args().size());
-
-      Encode22c(::dex::Opcode::OP_IGET,
-                RegisterValue(*instruction.dest()),
-                RegisterValue(args[0]),
-                instruction.index_argument());
-      break;
-    }
-    case Instruction::Op::kSetInstanceField: {
-      CHECK(!instruction.dest().has_value());
-      CHECK_EQ(2, args.size());
-      CHECK(args[0].is_variable());
-      CHECK(args[1].is_variable());
-
-      Encode22c(::dex::Opcode::OP_IPUT,
-                RegisterValue(args[1]),
-                RegisterValue(args[0]),
-                instruction.index_argument());
-      break;
-    }
-    default: { LOG(FATAL) << "Unsupported field operation"; }
-  }
-}
-
-size_t MethodBuilder::RegisterValue(const Value& value) const {
-  if (value.is_register()) {
-    return value.value();
-  } else if (value.is_parameter()) {
-    return value.value() + NumRegisters() + kMaxScratchRegisters;
-  }
-  CHECK(false && "Must be either a parameter or a register");
-  return 0;
-}
-
-void MethodBuilder::BindLabel(const Value& label_id) {
-  CHECK(label_id.is_label());
-
-  LabelData& label = labels_[label_id.value()];
-  CHECK(!label.bound_address.has_value());
-
-  label.bound_address = buffer_.size();
-
-  // patch any forward references to this label.
-  for (const auto& ref : label.references) {
-    buffer_[ref.field_offset] = *label.bound_address - ref.instruction_offset;
-  }
-  // No point keeping these around anymore.
-  label.references.clear();
-}
-
-::dex::u2 MethodBuilder::LabelValue(const Value& label_id, size_t instruction_offset,
-                                    size_t field_offset) {
-  CHECK(label_id.is_label());
-  LabelData& label = labels_[label_id.value()];
-
-  // Short-circuit if the label is already bound.
-  if (label.bound_address.has_value()) {
-    return *label.bound_address - instruction_offset;
-  }
-
-  // Otherwise, save a reference to where we need to back-patch later.
-  label.references.push_front(LabelReference{instruction_offset, field_offset});
-  return 0;
-}
-
-const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
-                                                     Prototype prototype) {
-  MethodDeclData& entry = method_id_map_[{type, name, prototype}];
-
-  if (entry.decl == nullptr) {
-    // This method has not already been declared, so declare it.
-    ir::MethodDecl* decl = dex_file_->Alloc<ir::MethodDecl>();
-    // The method id is the last added method.
-    size_t id = dex_file_->methods.size() - 1;
-
-    ir::String* dex_name{GetOrAddString(name)};
-    decl->name = dex_name;
-    decl->parent = GetOrAddType(type.descriptor());
-    decl->prototype = GetOrEncodeProto(prototype);
-
-    // update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc)
-    auto new_index = dex_file_->methods_indexes.AllocateIndex();
-    auto& ir_node = dex_file_->methods_map[new_index];
-    CHECK(ir_node == nullptr);
-    ir_node = decl;
-    decl->orig_index = decl->index = new_index;
-
-    entry = {id, decl};
-  }
-
-  return entry;
-}
-
-std::optional<const Prototype> DexBuilder::GetPrototypeByMethodId(size_t method_id) const {
-  for (const auto& entry : method_id_map_) {
-    if (entry.second.id == method_id) {
-      return entry.first.prototype;
-    }
-  }
-  return {};
-}
-
-ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) {
-  ir::Proto*& ir_proto = proto_map_[prototype];
-  if (ir_proto == nullptr) {
-    ir_proto = prototype.Encode(this);
-  }
-  return ir_proto;
-}
-
-}  // namespace dex
-}  // namespace startop
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
deleted file mode 100644
index eb2dc88..0000000
--- a/startop/view_compiler/dex_builder.h
+++ /dev/null
@@ -1,626 +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.
- */
-#ifndef DEX_BUILDER_H_
-#define DEX_BUILDER_H_
-
-#include <array>
-#include <forward_list>
-#include <map>
-#include <optional>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "android-base/logging.h"
-
-#include "slicer/dex_bytecode.h"
-#include "slicer/dex_ir.h"
-#include "slicer/writer.h"
-
-namespace startop {
-namespace dex {
-
-// TODO: remove this once the dex generation code is complete.
-void WriteTestDexFile(const std::string& filename);
-
-//////////////////////////
-// Forward declarations //
-//////////////////////////
-class DexBuilder;
-
-// Our custom allocator for dex::Writer
-//
-// This keeps track of all allocations and ensures they are freed when
-// TrackingAllocator is destroyed. Pointers to memory allocated by this
-// allocator must not outlive the allocator.
-class TrackingAllocator : public ::dex::Writer::Allocator {
- public:
-  virtual void* Allocate(size_t size);
-  virtual void Free(void* ptr);
-
- private:
-  std::unordered_map<void*, std::unique_ptr<uint8_t[]>> allocations_;
-};
-
-// Represents a DEX type descriptor.
-//
-// TODO: add a way to create a descriptor for a reference of a class type.
-class TypeDescriptor {
- public:
-  // Named constructors for base type descriptors.
-  static const TypeDescriptor Int();
-  static const TypeDescriptor Void();
-
-  // Creates a type descriptor from a fully-qualified class name. For example, it turns the class
-  // name java.lang.Object into the descriptor Ljava/lang/Object.
-  static TypeDescriptor FromClassname(const std::string& name);
-
-  // Return the full descriptor, such as I or Ljava/lang/Object
-  const std::string& descriptor() const { return descriptor_; }
-  // Return the shorty descriptor, such as I or L
-  std::string short_descriptor() const { return descriptor().substr(0, 1); }
-
-  bool is_object() const { return short_descriptor() == "L"; }
-
-  bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; }
-
- private:
-  explicit TypeDescriptor(std::string descriptor) : descriptor_{descriptor} {}
-
-  const std::string descriptor_;
-};
-
-// Defines a function signature. For example, Prototype{TypeDescriptor::VOID, TypeDescriptor::Int}
-// represents the function type (Int) -> Void.
-class Prototype {
- public:
-  template <typename... TypeDescriptors>
-  explicit Prototype(TypeDescriptor return_type, TypeDescriptors... param_types)
-      : return_type_{return_type}, param_types_{param_types...} {}
-
-  // Encode this prototype into the dex file.
-  ir::Proto* Encode(DexBuilder* dex) const;
-
-  // Get the shorty descriptor, such as VII for (Int, Int) -> Void
-  std::string Shorty() const;
-
-  const TypeDescriptor& ArgType(size_t index) const;
-
-  bool operator<(const Prototype& rhs) const {
-    return std::make_tuple(return_type_, param_types_) <
-           std::make_tuple(rhs.return_type_, rhs.param_types_);
-  }
-
- private:
-  const TypeDescriptor return_type_;
-  const std::vector<TypeDescriptor> param_types_;
-};
-
-// Represents a DEX register or constant. We separate regular registers and parameters
-// because we will not know the real parameter id until after all instructions
-// have been generated.
-class Value {
- public:
-  static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; }
-  static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; }
-  static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; }
-  static constexpr Value String(size_t value) { return Value{value, Kind::kString}; }
-  static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; }
-  static constexpr Value Type(size_t id) { return Value{id, Kind::kType}; }
-
-  bool is_register() const { return kind_ == Kind::kLocalRegister; }
-  bool is_parameter() const { return kind_ == Kind::kParameter; }
-  bool is_variable() const { return is_register() || is_parameter(); }
-  bool is_immediate() const { return kind_ == Kind::kImmediate; }
-  bool is_string() const { return kind_ == Kind::kString; }
-  bool is_label() const { return kind_ == Kind::kLabel; }
-  bool is_type() const { return kind_ == Kind::kType; }
-
-  size_t value() const { return value_; }
-
-  constexpr Value() : value_{0}, kind_{Kind::kInvalid} {}
-
- private:
-  enum class Kind { kInvalid, kLocalRegister, kParameter, kImmediate, kString, kLabel, kType };
-
-  size_t value_;
-  Kind kind_;
-
-  constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {}
-};
-
-// Represents an allocated register returned by MethodBuilder::AllocRegister
-class LiveRegister {
-  friend class MethodBuilder;
-
- public:
-  LiveRegister(LiveRegister&& other) : liveness_{other.liveness_}, index_{other.index_} {
-    other.index_ = {};
-  };
-  ~LiveRegister() {
-    if (index_.has_value()) {
-      (*liveness_)[*index_] = false;
-    }
-  };
-
-  operator const Value() const { return Value::Local(*index_); }
-
- private:
-  LiveRegister(std::vector<bool>* liveness, size_t index) : liveness_{liveness}, index_{index} {}
-
-  std::vector<bool>* const liveness_;
-  std::optional<size_t> index_;
-};
-
-// A virtual instruction. We convert these to real instructions in MethodBuilder::Encode.
-// Virtual instructions are needed to keep track of information that is not known until all of the
-// code is generated. This information includes things like how many local registers are created and
-// branch target locations.
-class Instruction {
- public:
-  // The operation performed by this instruction. These are virtual instructions that do not
-  // correspond exactly to DEX instructions.
-  enum class Op {
-    kBindLabel,
-    kBranchEqz,
-    kBranchNEqz,
-    kCheckCast,
-    kGetInstanceField,
-    kGetStaticField,
-    kInvokeDirect,
-    kInvokeInterface,
-    kInvokeStatic,
-    kInvokeVirtual,
-    kMove,
-    kMoveObject,
-    kNew,
-    kReturn,
-    kReturnObject,
-    kSetInstanceField,
-    kSetStaticField
-  };
-
-  ////////////////////////
-  // Named Constructors //
-  ////////////////////////
-
-  // For instructions with no return value and no arguments.
-  static inline Instruction OpNoArgs(Op opcode) {
-    return Instruction{opcode, /*index_argument*/ 0, /*dest*/ {}};
-  }
-  // For most instructions, which take some number of arguments and have an optional return value.
-  template <typename... T>
-  static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest,
-                                       const T&... args) {
-    return Instruction{opcode, /*index_argument=*/0, /*result_is_object=*/false, dest, args...};
-  }
-
-  // A cast instruction. Basically, `(type)val`
-  static inline Instruction Cast(Value val, Value type) {
-    CHECK(type.is_type());
-    return OpWithArgs(Op::kCheckCast, val, type);
-  }
-
-  // For method calls.
-  template <typename... T>
-  static inline Instruction InvokeVirtual(size_t index_argument, std::optional<const Value> dest,
-                                          Value this_arg, T... args) {
-    return Instruction{
-        Op::kInvokeVirtual, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
-  }
-  // Returns an object
-  template <typename... T>
-  static inline Instruction InvokeVirtualObject(size_t index_argument,
-                                                std::optional<const Value> dest, Value this_arg,
-                                                const T&... args) {
-    return Instruction{
-        Op::kInvokeVirtual, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
-  }
-  // For direct calls (basically, constructors).
-  template <typename... T>
-  static inline Instruction InvokeDirect(size_t index_argument, std::optional<const Value> dest,
-                                         Value this_arg, const T&... args) {
-    return Instruction{
-        Op::kInvokeDirect, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
-  }
-  // Returns an object
-  template <typename... T>
-  static inline Instruction InvokeDirectObject(size_t index_argument,
-                                               std::optional<const Value> dest, Value this_arg,
-                                               T... args) {
-    return Instruction{
-        Op::kInvokeDirect, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
-  }
-  // For static calls.
-  template <typename... T>
-  static inline Instruction InvokeStatic(size_t index_argument, std::optional<const Value> dest,
-                                         T... args) {
-    return Instruction{
-        Op::kInvokeStatic, index_argument, /*result_is_object=*/false, dest, args...};
-  }
-  // Returns an object
-  template <typename... T>
-  static inline Instruction InvokeStaticObject(size_t index_argument,
-                                               std::optional<const Value> dest, T... args) {
-    return Instruction{Op::kInvokeStatic, index_argument, /*result_is_object=*/true, dest, args...};
-  }
-  // For static calls.
-  template <typename... T>
-  static inline Instruction InvokeInterface(size_t index_argument, std::optional<const Value> dest,
-                                            const T&... args) {
-    return Instruction{
-        Op::kInvokeInterface, index_argument, /*result_is_object=*/false, dest, args...};
-  }
-
-  static inline Instruction GetStaticField(size_t field_id, Value dest) {
-    return Instruction{Op::kGetStaticField, field_id, dest};
-  }
-
-  static inline Instruction SetStaticField(size_t field_id, Value value) {
-    return Instruction{
-        Op::kSetStaticField, field_id, /*result_is_object=*/false, /*dest=*/{}, value};
-  }
-
-  static inline Instruction GetField(size_t field_id, Value dest, Value object) {
-    return Instruction{Op::kGetInstanceField, field_id, /*result_is_object=*/false, dest, object};
-  }
-
-  static inline Instruction SetField(size_t field_id, Value object, Value value) {
-    return Instruction{
-        Op::kSetInstanceField, field_id, /*result_is_object=*/false, /*dest=*/{}, object, value};
-  }
-
-  ///////////////
-  // Accessors //
-  ///////////////
-
-  Op opcode() const { return opcode_; }
-  size_t index_argument() const { return index_argument_; }
-  bool result_is_object() const { return result_is_object_; }
-  const std::optional<const Value>& dest() const { return dest_; }
-  const std::vector<const Value>& args() const { return args_; }
-
- private:
-  inline Instruction(Op opcode, size_t index_argument, std::optional<const Value> dest)
-      : opcode_{opcode},
-        index_argument_{index_argument},
-        result_is_object_{false},
-        dest_{dest},
-        args_{} {}
-
-  template <typename... T>
-  inline Instruction(Op opcode, size_t index_argument, bool result_is_object,
-                     std::optional<const Value> dest, const T&... args)
-      : opcode_{opcode},
-        index_argument_{index_argument},
-        result_is_object_{result_is_object},
-        dest_{dest},
-        args_{args...} {}
-
-  const Op opcode_;
-  // The index of the method to invoke, for kInvokeVirtual and similar opcodes.
-  const size_t index_argument_{0};
-  const bool result_is_object_;
-  const std::optional<const Value> dest_;
-  const std::vector<const Value> args_;
-};
-
-// Needed for CHECK_EQ, DCHECK_EQ, etc.
-std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode);
-
-// Keeps track of information needed to manipulate or call a method.
-struct MethodDeclData {
-  size_t id;
-  ir::MethodDecl* decl;
-};
-
-// Tools to help build methods and their bodies.
-class MethodBuilder {
- public:
-  MethodBuilder(DexBuilder* dex, ir::Class* class_def, ir::MethodDecl* decl);
-
-  // Encode the method into DEX format.
-  ir::EncodedMethod* Encode();
-
-  // Create a new register to be used to storing values.
-  LiveRegister AllocRegister();
-
-  Value MakeLabel();
-
-  /////////////////////////////////
-  // Instruction builder methods //
-  /////////////////////////////////
-
-  void AddInstruction(Instruction instruction);
-
-  // return-void
-  void BuildReturn();
-  void BuildReturn(Value src, bool is_object = false);
-  // const/4
-  void BuildConst4(Value target, int value);
-  void BuildConstString(Value target, const std::string& value);
-  template <typename... T>
-  void BuildNew(Value target, TypeDescriptor type, Prototype constructor, const T&... args);
-
-  // TODO: add builders for more instructions
-
-  DexBuilder* dex_file() const { return dex_; }
-
- private:
-  void EncodeInstructions();
-  void EncodeInstruction(const Instruction& instruction);
-
-  // Encodes a return instruction. For instructions with no return value, the opcode field is
-  // ignored. Otherwise, this specifies which return instruction will be used (return,
-  // return-object, etc.)
-  void EncodeReturn(const Instruction& instruction, ::dex::Opcode opcode);
-
-  void EncodeMove(const Instruction& instruction);
-  void EncodeInvoke(const Instruction& instruction, ::dex::Opcode opcode);
-  void EncodeBranch(::dex::Opcode op, const Instruction& instruction);
-  void EncodeNew(const Instruction& instruction);
-  void EncodeCast(const Instruction& instruction);
-  void EncodeFieldOp(const Instruction& instruction);
-
-  // Low-level instruction format encoding. See
-  // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of
-  // formats.
-
-  inline uint8_t ToBits(::dex::Opcode opcode) {
-    static_assert(sizeof(uint8_t) == sizeof(::dex::Opcode));
-    return static_cast<uint8_t>(opcode);
-  }
-
-  inline void Encode10x(::dex::Opcode opcode) {
-    // 00|op
-    static_assert(sizeof(uint8_t) == sizeof(::dex::Opcode));
-    buffer_.push_back(ToBits(opcode));
-  }
-
-  inline void Encode11x(::dex::Opcode opcode, uint8_t a) {
-    // aa|op
-    buffer_.push_back((a << 8) | ToBits(opcode));
-  }
-
-  inline void Encode11n(::dex::Opcode opcode, uint8_t a, int8_t b) {
-    // b|a|op
-
-    // Make sure the fields are in bounds (4 bits for a, 4 bits for b).
-    CHECK_LT(a, 16);
-    CHECK_LE(-8, b);
-    CHECK_LT(b, 8);
-
-    buffer_.push_back(((b & 0xf) << 12) | (a << 8) | ToBits(opcode));
-  }
-
-  inline void Encode21c(::dex::Opcode opcode, uint8_t a, uint16_t b) {
-    // aa|op|bbbb
-    buffer_.push_back((a << 8) | ToBits(opcode));
-    buffer_.push_back(b);
-  }
-
-  inline void Encode22c(::dex::Opcode opcode, uint8_t a, uint8_t b, uint16_t c) {
-    // b|a|op|bbbb
-    CHECK(IsShortRegister(a));
-    CHECK(IsShortRegister(b));
-    buffer_.push_back((b << 12) | (a << 8) | ToBits(opcode));
-    buffer_.push_back(c);
-  }
-
-  inline void Encode32x(::dex::Opcode opcode, uint16_t a, uint16_t b) {
-    buffer_.push_back(ToBits(opcode));
-    buffer_.push_back(a);
-    buffer_.push_back(b);
-  }
-
-  inline void Encode35c(::dex::Opcode opcode, size_t a, uint16_t b, uint8_t c, uint8_t d,
-                        uint8_t e, uint8_t f, uint8_t g) {
-    // a|g|op|bbbb|f|e|d|c
-
-    CHECK_LE(a, 5);
-    CHECK(IsShortRegister(c));
-    CHECK(IsShortRegister(d));
-    CHECK(IsShortRegister(e));
-    CHECK(IsShortRegister(f));
-    CHECK(IsShortRegister(g));
-    buffer_.push_back((a << 12) | (g << 8) | ToBits(opcode));
-    buffer_.push_back(b);
-    buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c);
-  }
-
-  inline void Encode3rc(::dex::Opcode opcode, size_t a, uint16_t b, uint16_t c) {
-    CHECK_LE(a, 255);
-    buffer_.push_back((a << 8) | ToBits(opcode));
-    buffer_.push_back(b);
-    buffer_.push_back(c);
-  }
-
-  static constexpr bool IsShortRegister(size_t register_value) { return register_value < 16; }
-
-  // Returns an array of num_regs scratch registers. These are guaranteed to be
-  // contiguous, so they are suitable for the invoke-*/range instructions.
-  template <int num_regs>
-  std::array<Value, num_regs> GetScratchRegisters() const {
-    static_assert(num_regs <= kMaxScratchRegisters);
-    std::array<Value, num_regs> regs;
-    for (size_t i = 0; i < num_regs; ++i) {
-      regs[i] = std::move(Value::Local(NumRegisters() + i));
-    }
-    return regs;
-  }
-
-  // Converts a register or parameter to its DEX register number.
-  size_t RegisterValue(const Value& value) const;
-
-  // Sets a label's address to the current position in the instruction buffer. If there are any
-  // forward references to the label, this function will back-patch them.
-  void BindLabel(const Value& label);
-
-  // Returns the offset of the label relative to the given instruction offset. If the label is not
-  // bound, a reference will be saved and it will automatically be patched when the label is bound.
-  ::dex::u2 LabelValue(const Value& label, size_t instruction_offset, size_t field_offset);
-
-  DexBuilder* dex_;
-  ir::Class* class_;
-  ir::MethodDecl* decl_;
-
-  // A list of the instructions we will eventually encode.
-  std::vector<Instruction> instructions_;
-
-  // A buffer to hold instructions that have been encoded.
-  std::vector<::dex::u2> buffer_;
-
-  // We create some scratch registers for when we have to shuffle registers
-  // around to make legal DEX code.
-  static constexpr size_t kMaxScratchRegisters = 5;
-
-  size_t NumRegisters() const {
-    return register_liveness_.size();
-  }
-
-  // Stores information needed to back-patch a label once it is bound. We need to know the start of
-  // the instruction that refers to the label, and the offset to where the actual label value should
-  // go.
-  struct LabelReference {
-    size_t instruction_offset;
-    size_t field_offset;
-  };
-
-  struct LabelData {
-    std::optional<size_t> bound_address;
-    std::forward_list<LabelReference> references;
-  };
-
-  std::vector<LabelData> labels_;
-
-  // During encoding, keep track of the largest number of arguments needed, so we can use it for our
-  // outs count
-  size_t max_args_{0};
-
-  std::vector<bool> register_liveness_;
-};
-
-// A helper to build class definitions.
-class ClassBuilder {
- public:
-  ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def);
-
-  void set_source_file(const std::string& source);
-
-  // Create a method with the given name and prototype. The returned MethodBuilder can be used to
-  // fill in the method body.
-  MethodBuilder CreateMethod(const std::string& name, Prototype prototype);
-
- private:
-  DexBuilder* const parent_;
-  const TypeDescriptor type_descriptor_;
-  ir::Class* const class_;
-};
-
-// Builds Dex files from scratch.
-class DexBuilder {
- public:
-  DexBuilder();
-
-  // Create an in-memory image of the DEX file that can either be loaded directly or written to a
-  // file.
-  slicer::MemView CreateImage();
-
-  template <typename T>
-  T* Alloc() {
-    return dex_file_->Alloc<T>();
-  }
-
-  // Find the ir::String that matches the given string, creating it if it does not exist.
-  ir::String* GetOrAddString(const std::string& string);
-  // Create a new class of the given name.
-  ClassBuilder MakeClass(const std::string& name);
-
-  // Add a type for the given descriptor, or return the existing one if it already exists.
-  // See the TypeDescriptor class for help generating these. GetOrAddType can be used to declare
-  // imported classes.
-  ir::Type* GetOrAddType(const std::string& descriptor);
-  inline ir::Type* GetOrAddType(TypeDescriptor descriptor) {
-    return GetOrAddType(descriptor.descriptor());
-  }
-
-  ir::FieldDecl* GetOrAddField(TypeDescriptor parent, const std::string& name, TypeDescriptor type);
-
-  // Returns the method id for the method, creating it if it has not been created yet.
-  const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
-                                           Prototype prototype);
-
-  std::optional<const Prototype> GetPrototypeByMethodId(size_t method_id) const;
-
- private:
-  // Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not
-  // exist.
-  ir::Proto* GetOrEncodeProto(Prototype prototype);
-
-  std::shared_ptr<ir::DexFile> dex_file_;
-
-  // allocator_ is needed to be able to encode the image.
-  TrackingAllocator allocator_;
-
-  // We'll need to allocate buffers for all of the encoded strings we create. This is where we store
-  // all of them.
-  std::vector<std::unique_ptr<uint8_t[]>> string_data_;
-
-  // Keep track of what types we've defined so we can look them up later.
-  std::unordered_map<std::string, ir::Type*> types_by_descriptor_;
-
-  struct MethodDescriptor {
-    TypeDescriptor type;
-    std::string name;
-    Prototype prototype;
-
-    inline bool operator<(const MethodDescriptor& rhs) const {
-      return std::make_tuple(type, name, prototype) <
-             std::make_tuple(rhs.type, rhs.name, rhs.prototype);
-    }
-  };
-
-  // Maps method declarations to their method index. This is needed to encode references to them.
-  // When we go to actually write the DEX file, slicer will re-assign these after correctly sorting
-  // the methods list.
-  std::map<MethodDescriptor, MethodDeclData> method_id_map_;
-
-  // Keep track of what strings we've defined so we can look them up later.
-  std::unordered_map<std::string, ir::String*> strings_;
-
-  // Keep track of already-encoded protos.
-  std::map<Prototype, ir::Proto*> proto_map_;
-
-  // Keep track of fields that have been declared
-  std::map<std::tuple<TypeDescriptor, std::string>, ir::FieldDecl*> field_decls_by_key_;
-};
-
-template <typename... T>
-void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor,
-                             const T&... args) {
-  MethodDeclData constructor_data{dex_->GetOrDeclareMethod(type, "<init>", constructor)};
-  // allocate the object
-  ir::Type* type_def = dex_->GetOrAddType(type.descriptor());
-  AddInstruction(
-      Instruction::OpWithArgs(Instruction::Op::kNew, target, Value::Type(type_def->orig_index)));
-  // call the constructor
-  AddInstruction(Instruction::InvokeDirect(constructor_data.id, /*dest=*/{}, target, args...));
-};
-
-}  // namespace dex
-}  // namespace startop
-
-#endif  // DEX_BUILDER_H_
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
deleted file mode 100644
index bcba2fe..0000000
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ /dev/null
@@ -1,68 +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 {
-    // 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"],
-}
-
-genrule {
-    name: "generate_compiled_layout1",
-    tools: [":viewcompiler"],
-    cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test",
-    srcs: ["res/layout/layout1.xml"],
-    out: [
-        "layout1.dex",
-    ],
-}
-
-genrule {
-    name: "generate_compiled_layout2",
-    tools: [":viewcompiler"],
-    cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test",
-    srcs: ["res/layout/layout2.xml"],
-    out: [
-        "layout2.dex",
-    ],
-}
-
-android_test {
-    name: "dex-builder-test",
-    srcs: [
-        "src/android/startop/test/DexBuilderTest.java",
-        "src/android/startop/test/LayoutCompilerTest.java",
-        "src/android/startop/test/TestClass.java",
-    ],
-    sdk_version: "current",
-    data: [
-        ":generate_dex_testcases",
-        ":generate_compiled_layout1",
-        ":generate_compiled_layout2",
-    ],
-    static_libs: [
-        "androidx.test.core",
-        "androidx.test.runner",
-        "junit",
-    ],
-    manifest: "AndroidManifest.xml",
-    resource_dirs: ["res"],
-    test_config: "AndroidTest.xml",
-    test_suites: ["general-tests"],
-}
diff --git a/startop/view_compiler/dex_builder_test/AndroidManifest.xml b/startop/view_compiler/dex_builder_test/AndroidManifest.xml
deleted file mode 100644
index b335663..0000000
--- a/startop/view_compiler/dex_builder_test/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.startop.test" >
-
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.startop.test"
-                     android:label="DexBuilder Tests"/>
-
-</manifest>
diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml
deleted file mode 100644
index 59093c7..0000000
--- a/startop/view_compiler/dex_builder_test/AndroidTest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Runs DexBuilder 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="dex-builder-test.apk" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
-        <option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
-        <option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" />
-        <option name="push" value="layout2.dex->/data/local/tmp/dex-builder-test/layout2.dex" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.startop.test" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-    </test>
-</configuration>
diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout1.xml b/startop/view_compiler/dex_builder_test/res/layout/layout1.xml
deleted file mode 100644
index 0f9375c..0000000
--- a/startop/view_compiler/dex_builder_test/res/layout/layout1.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-   android:layout_width="match_parent"
-   android:layout_height="match_parent"
-   android:paddingLeft="16dp"
-   android:paddingRight="16dp"
-   android:orientation="vertical"
-   android:gravity="center">
-
-    <Button
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
-    <Button
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
-
- </LinearLayout>
diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout2.xml b/startop/view_compiler/dex_builder_test/res/layout/layout2.xml
deleted file mode 100644
index b092e1c..0000000
--- a/startop/view_compiler/dex_builder_test/res/layout/layout2.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <TableRow
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" >
-
-        <Button
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Button" />
-
-        <TableRow
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" >
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Button" />
-            <TableRow
-                android:layout_width="match_parent"
-                android:layout_height="match_parent" >
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="Button" />
-
-                <TableRow
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent" >
-
-                    <Button
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="Button" />
-                </TableRow>
-
-            </TableRow>
-        </TableRow>
-    </TableRow>
-</LinearLayout>
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
deleted file mode 100644
index 6af01f6f..0000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ /dev/null
@@ -1,213 +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 android.startop.test;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import dalvik.system.PathClassLoader;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-// Adding tests here requires changes in several other places. See README.md in
-// the view_compiler directory for more information.
-public final class DexBuilderTest {
-  static ClassLoader loadDexFile(String filename) throws Exception {
-    return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
-        DexBuilderTest.class.getClassLoader());
-  }
-
-  public void hello() {}
-
-  @Test
-  public void loadTrivialDex() throws Exception {
-    ClassLoader loader = loadDexFile("trivial.dex");
-    loader.loadClass("android.startop.test.testcases.Trivial");
-  }
-
-  @Test
-  public void return5() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("return5");
-    Assert.assertEquals(5, method.invoke(null));
-  }
-
-  @Test
-  public void returnInteger5() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnInteger5");
-    Assert.assertEquals(5, method.invoke(null));
-  }
-
-  @Test
-  public void returnParam() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnParam", int.class);
-    Assert.assertEquals(5, method.invoke(null, 5));
-    Assert.assertEquals(42, method.invoke(null, 42));
-  }
-
-  @Test
-  public void returnStringLength() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnStringLength", String.class);
-    Assert.assertEquals(13, method.invoke(null, "Hello, World!"));
-  }
-
-  @Test
-  public void returnIfZero() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnIfZero", int.class);
-    Assert.assertEquals(5, method.invoke(null, 0));
-    Assert.assertEquals(3, method.invoke(null, 17));
-  }
-
-  @Test
-  public void returnIfNotZero() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnIfNotZero", int.class);
-    Assert.assertEquals(3, method.invoke(null, 0));
-    Assert.assertEquals(5, method.invoke(null, 17));
-  }
-
-  @Test
-  public void backwardsBranch() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("backwardsBranch");
-    Assert.assertEquals(2, method.invoke(null));
-  }
-
-  @Test
-  public void returnNull() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnNull");
-    Assert.assertEquals(null, method.invoke(null));
-  }
-
-  @Test
-  public void makeString() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("makeString");
-    Assert.assertEquals("Hello, World!", method.invoke(null));
-  }
-
-  @Test
-  public void returnStringIfZeroAB() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnStringIfZeroAB", int.class);
-    Assert.assertEquals("a", method.invoke(null, 0));
-    Assert.assertEquals("b", method.invoke(null, 1));
-  }
-
-  @Test
-  public void returnStringIfZeroBA() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("returnStringIfZeroBA", int.class);
-    Assert.assertEquals("b", method.invoke(null, 0));
-    Assert.assertEquals("a", method.invoke(null, 1));
-  }
-
-  @Test
-  public void invokeStaticReturnObject() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("invokeStaticReturnObject", int.class, int.class);
-    Assert.assertEquals("10", method.invoke(null, 10, 10));
-    Assert.assertEquals("a", method.invoke(null, 10, 16));
-    Assert.assertEquals("5", method.invoke(null, 5, 16));
-  }
-
-  @Test
-  public void invokeVirtualReturnObject() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class);
-    Assert.assertEquals("bc", method.invoke(null, "abc", 1));
-  }
-
-  @Test
-  public void castObjectToString() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("castObjectToString", Object.class);
-    Assert.assertEquals("abc", method.invoke(null, "abc"));
-    boolean castFailed = false;
-    try {
-      method.invoke(null, 5);
-    } catch (InvocationTargetException e) {
-      if (e.getCause() instanceof ClassCastException) {
-        castFailed = true;
-      } else {
-        throw e;
-      }
-    }
-    Assert.assertTrue(castFailed);
-  }
-
-  @Test
-  public void readStaticField() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("readStaticField");
-    TestClass.staticInteger = 5;
-    Assert.assertEquals(5, method.invoke(null));
-  }
-
-  @Test
-  public void setStaticField() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("setStaticField");
-    TestClass.staticInteger = 5;
-    method.invoke(null);
-    Assert.assertEquals(7, TestClass.staticInteger);
-  }
-
-  @Test
-  public void readInstanceField() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("readInstanceField", TestClass.class);
-    TestClass obj = new TestClass();
-    obj.instanceField = 5;
-    Assert.assertEquals(5, method.invoke(null, obj));
-  }
-
-  @Test
-  public void setInstanceField() throws Exception {
-    ClassLoader loader = loadDexFile("simple.dex");
-    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
-    Method method = clazz.getMethod("setInstanceField", TestClass.class);
-    TestClass obj = new TestClass();
-    obj.instanceField = 5;
-    method.invoke(null, obj);
-    Assert.assertEquals(7, obj.instanceField);
-  }
-}
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
deleted file mode 100644
index b0cf91d..0000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
+++ /dev/null
@@ -1,52 +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 android.startop.test;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import android.view.View;
-import dalvik.system.PathClassLoader;
-import java.lang.reflect.Method;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.lang.reflect.Method;
-
-// Adding tests here requires changes in several other places. See README.md in
-// the view_compiler directory for more information.
-public class LayoutCompilerTest {
-    static ClassLoader loadDexFile(String filename) throws Exception {
-        return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
-                ClassLoader.getSystemClassLoader());
-    }
-
-    @Test
-    public void loadAndInflateLayout1() throws Exception {
-        ClassLoader dex_file = loadDexFile("layout1.dex");
-        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
-        Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
-        Context context = InstrumentationRegistry.getTargetContext();
-        layout1.invoke(null, context, R.layout.layout1);
-    }
-
-    @Test
-    public void loadAndInflateLayout2() throws Exception {
-        ClassLoader dex_file = loadDexFile("layout2.dex");
-        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
-        Method layout1 = compiled_view.getMethod("layout2", Context.class, int.class);
-        Context context = InstrumentationRegistry.getTargetContext();
-        layout1.invoke(null, context, R.layout.layout1);
-    }
-}
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java
deleted file mode 100644
index dd77923..0000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java
+++ /dev/null
@@ -1,23 +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 android.startop.test;
-
- /**
- * A simple class to help test DexBuilder.
- */
-public final class TestClass {
-    public static int staticInteger;
-
-    public int instanceField;
-}
diff --git a/startop/view_compiler/dex_layout_compiler.cc b/startop/view_compiler/dex_layout_compiler.cc
deleted file mode 100644
index bddb8aa..0000000
--- a/startop/view_compiler/dex_layout_compiler.cc
+++ /dev/null
@@ -1,208 +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.
- */
-
-#include "dex_layout_compiler.h"
-#include "layout_validation.h"
-
-#include "android-base/stringprintf.h"
-
-namespace startop {
-
-using android::base::StringPrintf;
-using dex::Instruction;
-using dex::LiveRegister;
-using dex::Prototype;
-using dex::TypeDescriptor;
-using dex::Value;
-
-namespace {
-// TODO: these are a bunch of static initializers, which we should avoid. See if
-// we can make them constexpr.
-const TypeDescriptor kAttributeSet = TypeDescriptor::FromClassname("android.util.AttributeSet");
-const TypeDescriptor kContext = TypeDescriptor::FromClassname("android.content.Context");
-const TypeDescriptor kLayoutInflater = TypeDescriptor::FromClassname("android.view.LayoutInflater");
-const TypeDescriptor kResources = TypeDescriptor::FromClassname("android.content.res.Resources");
-const TypeDescriptor kString = TypeDescriptor::FromClassname("java.lang.String");
-const TypeDescriptor kView = TypeDescriptor::FromClassname("android.view.View");
-const TypeDescriptor kViewGroup = TypeDescriptor::FromClassname("android.view.ViewGroup");
-const TypeDescriptor kXmlResourceParser =
-    TypeDescriptor::FromClassname("android.content.res.XmlResourceParser");
-}  // namespace
-
-DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method)
-    : method_{method},
-      context_{Value::Parameter(0)},
-      resid_{Value::Parameter(1)},
-      inflater_{method->AllocRegister()},
-      xml_{method->AllocRegister()},
-      attrs_{method->AllocRegister()},
-      classname_tmp_{method->AllocRegister()},
-      xml_next_{method->dex_file()->GetOrDeclareMethod(kXmlResourceParser, "next",
-                                                       Prototype{TypeDescriptor::Int()})},
-      try_create_view_{method->dex_file()->GetOrDeclareMethod(
-          kLayoutInflater, "tryCreateView",
-          Prototype{kView, kView, kString, kContext, kAttributeSet})},
-      generate_layout_params_{method->dex_file()->GetOrDeclareMethod(
-          kViewGroup, "generateLayoutParams",
-          Prototype{TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"),
-                    kAttributeSet})},
-      add_view_{method->dex_file()->GetOrDeclareMethod(
-          kViewGroup, "addView",
-          Prototype{TypeDescriptor::Void(),
-                    kView,
-                    TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})} {}
-
-void DexViewBuilder::BuildGetLayoutInflater(Value dest) {
-  // dest = LayoutInflater.from(context);
-  auto layout_inflater_from = method_->dex_file()->GetOrDeclareMethod(
-      kLayoutInflater, "from", Prototype{kLayoutInflater, kContext});
-  method_->AddInstruction(Instruction::InvokeStaticObject(layout_inflater_from.id, dest, context_));
-}
-
-void DexViewBuilder::BuildGetResources(Value dest) {
-  // dest = context.getResources();
-  auto get_resources =
-      method_->dex_file()->GetOrDeclareMethod(kContext, "getResources", Prototype{kResources});
-  method_->AddInstruction(Instruction::InvokeVirtualObject(get_resources.id, dest, context_));
-}
-
-void DexViewBuilder::BuildGetLayoutResource(Value dest, Value resources, Value resid) {
-  // dest = resources.getLayout(resid);
-  auto get_layout = method_->dex_file()->GetOrDeclareMethod(
-      kResources, "getLayout", Prototype{kXmlResourceParser, TypeDescriptor::Int()});
-  method_->AddInstruction(Instruction::InvokeVirtualObject(get_layout.id, dest, resources, resid));
-}
-
-void DexViewBuilder::BuildLayoutResourceToAttributeSet(dex::Value dest,
-                                                       dex::Value layout_resource) {
-  // dest = Xml.asAttributeSet(layout_resource);
-  auto as_attribute_set = method_->dex_file()->GetOrDeclareMethod(
-      TypeDescriptor::FromClassname("android.util.Xml"),
-      "asAttributeSet",
-      Prototype{kAttributeSet, TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")});
-  method_->AddInstruction(
-      Instruction::InvokeStaticObject(as_attribute_set.id, dest, layout_resource));
-}
-
-void DexViewBuilder::BuildXmlNext() {
-  // xml_.next();
-  method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_));
-}
-
-void DexViewBuilder::Start() {
-  BuildGetLayoutInflater(/*dest=*/inflater_);
-  BuildGetResources(/*dest=*/xml_);
-  BuildGetLayoutResource(/*dest=*/xml_, /*resources=*/xml_, resid_);
-  BuildLayoutResourceToAttributeSet(/*dest=*/attrs_, /*layout_resource=*/xml_);
-
-  // Advance past start document tag
-  BuildXmlNext();
-}
-
-void DexViewBuilder::Finish() {}
-
-namespace {
-std::string ResolveName(const std::string& name) {
-  if (name == "View") return "android.view.View";
-  if (name == "ViewGroup") return "android.view.ViewGroup";
-  if (name.find('.') == std::string::npos) {
-    return StringPrintf("android.widget.%s", name.c_str());
-  }
-  return name;
-}
-}  // namespace
-
-void DexViewBuilder::BuildTryCreateView(Value dest, Value parent, Value classname) {
-  // dest = inflater_.tryCreateView(parent, classname, context_, attrs_);
-  method_->AddInstruction(Instruction::InvokeVirtualObject(
-      try_create_view_.id, dest, inflater_, parent, classname, context_, attrs_));
-}
-
-void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) {
-  bool const is_root_view = view_stack_.empty();
-
-  // Advance to start tag
-  BuildXmlNext();
-
-  LiveRegister view = AcquireRegister();
-  // try to create the view using the factories
-  method_->BuildConstString(classname_tmp_,
-                            name);  // TODO: the need to fully qualify the classname
-  if (is_root_view) {
-    LiveRegister null = AcquireRegister();
-    method_->BuildConst4(null, 0);
-    BuildTryCreateView(/*dest=*/view, /*parent=*/null, classname_tmp_);
-  } else {
-    BuildTryCreateView(/*dest=*/view, /*parent=*/GetCurrentView(), classname_tmp_);
-  }
-  auto label = method_->MakeLabel();
-  // branch if not null
-  method_->AddInstruction(
-      Instruction::OpWithArgs(Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label));
-
-  // If null, create the class directly.
-  method_->BuildNew(view,
-                    TypeDescriptor::FromClassname(ResolveName(name)),
-                    Prototype{TypeDescriptor::Void(), kContext, kAttributeSet},
-                    context_,
-                    attrs_);
-
-  method_->AddInstruction(Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, label));
-
-  if (is_viewgroup) {
-    // Cast to a ViewGroup so we can add children later.
-    const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(kViewGroup.descriptor());
-    method_->AddInstruction(Instruction::Cast(view, Value::Type(view_group_def->orig_index)));
-  }
-
-  if (!is_root_view) {
-    // layout_params = parent.generateLayoutParams(attrs);
-    LiveRegister layout_params{AcquireRegister()};
-    method_->AddInstruction(Instruction::InvokeVirtualObject(
-        generate_layout_params_.id, layout_params, GetCurrentView(), attrs_));
-    view_stack_.push_back({std::move(view), std::move(layout_params)});
-  } else {
-    view_stack_.push_back({std::move(view), {}});
-  }
-}
-
-void DexViewBuilder::FinishView() {
-  if (view_stack_.size() == 1) {
-    method_->BuildReturn(GetCurrentView(), /*is_object=*/true);
-  } else {
-    // parent.add(view, layout_params)
-    method_->AddInstruction(Instruction::InvokeVirtual(
-        add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams()));
-    // xml.next(); // end tag
-    method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_));
-  }
-  PopViewStack();
-}
-
-LiveRegister DexViewBuilder::AcquireRegister() { return method_->AllocRegister(); }
-
-Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; }
-Value DexViewBuilder::GetCurrentLayoutParams() const {
-  return view_stack_.back().layout_params.value();
-}
-Value DexViewBuilder::GetParentView() const { return view_stack_[view_stack_.size() - 2].view; }
-
-void DexViewBuilder::PopViewStack() {
-  // Unconditionally release the view register.
-  view_stack_.pop_back();
-}
-
-}  // namespace startop
diff --git a/startop/view_compiler/dex_layout_compiler.h b/startop/view_compiler/dex_layout_compiler.h
deleted file mode 100644
index a34ed1f..0000000
--- a/startop/view_compiler/dex_layout_compiler.h
+++ /dev/null
@@ -1,123 +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.
- */
-
-#ifndef DEX_LAYOUT_COMPILER_H_
-#define DEX_LAYOUT_COMPILER_H_
-
-#include "dex_builder.h"
-
-#include <codecvt>
-#include <locale>
-#include <string>
-#include <vector>
-
-namespace startop {
-
-// This visitor does the actual view compilation, using a supplied builder.
-template <typename Builder>
-class LayoutCompilerVisitor {
- public:
-  explicit LayoutCompilerVisitor(Builder* builder) : builder_{builder} {}
-
-  void VisitStartDocument() { builder_->Start(); }
-  void VisitEndDocument() { builder_->Finish(); }
-  void VisitStartTag(const std::u16string& name) {
-    parent_stack_.push_back(ViewEntry{
-        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(name), {}});
-  }
-  void VisitEndTag() {
-    auto entry = parent_stack_.back();
-    parent_stack_.pop_back();
-
-    if (parent_stack_.empty()) {
-      GenerateCode(entry);
-    } else {
-      parent_stack_.back().children.push_back(entry);
-    }
-  }
-
- private:
-  struct ViewEntry {
-    std::string name;
-    std::vector<ViewEntry> children;
-  };
-
-  void GenerateCode(const ViewEntry& view) {
-    builder_->StartView(view.name, !view.children.empty());
-    for (const auto& child : view.children) {
-      GenerateCode(child);
-    }
-    builder_->FinishView();
-  }
-
-  Builder* builder_;
-
-  std::vector<ViewEntry> parent_stack_;
-};
-
-class DexViewBuilder {
- public:
-  DexViewBuilder(dex::MethodBuilder* method);
-
-  void Start();
-  void Finish();
-  void StartView(const std::string& name, bool is_viewgroup);
-  void FinishView();
-
- private:
-  // Accessors for the stack of views that are under construction.
-  dex::LiveRegister AcquireRegister();
-  dex::Value GetCurrentView() const;
-  dex::Value GetCurrentLayoutParams() const;
-  dex::Value GetParentView() const;
-  void PopViewStack();
-
-  // Methods to simplify building different code fragments.
-  void BuildGetLayoutInflater(dex::Value dest);
-  void BuildGetResources(dex::Value dest);
-  void BuildGetLayoutResource(dex::Value dest, dex::Value resources, dex::Value resid);
-  void BuildLayoutResourceToAttributeSet(dex::Value dest, dex::Value layout_resource);
-  void BuildXmlNext();
-  void BuildTryCreateView(dex::Value dest, dex::Value parent, dex::Value classname);
-
-  dex::MethodBuilder* method_;
-
-  // Parameters to the generated method
-  dex::Value const context_;
-  dex::Value const resid_;
-
-  // Registers used for code generation
-  const dex::LiveRegister inflater_;
-  const dex::LiveRegister xml_;
-  const dex::LiveRegister attrs_;
-  const dex::LiveRegister classname_tmp_;
-
-  const dex::MethodDeclData xml_next_;
-  const dex::MethodDeclData try_create_view_;
-  const dex::MethodDeclData generate_layout_params_;
-  const dex::MethodDeclData add_view_;
-
-  // Keep track of the views currently in progress.
-  struct ViewEntry {
-    dex::LiveRegister view;
-    std::optional<dex::LiveRegister> layout_params;
-  };
-  std::vector<ViewEntry> view_stack_;
-};
-
-}  // namespace startop
-
-#endif  // DEX_LAYOUT_COMPILER_H_
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
deleted file mode 100644
index 5dda59e..0000000
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ /dev/null
@@ -1,353 +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.
- */
-
-#include "android-base/logging.h"
-#include "dex_builder.h"
-
-#include <fstream>
-#include <string>
-
-// Adding tests here requires changes in several other places. See README.md in
-// the view_compiler directory for more information.
-
-using namespace startop::dex;
-using namespace std;
-
-void GenerateTrivialDexFile(const string& outdir) {
-  DexBuilder dex_file;
-
-  ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.Trivial")};
-  cbuilder.set_source_file("dex_testcase_generator.cc#GenerateTrivialDexFile");
-
-  slicer::MemView image{dex_file.CreateImage()};
-  std::ofstream out_file(outdir + "/trivial.dex");
-  out_file.write(image.ptr<const char>(), image.size());
-}
-
-// Generates test cases that test around 1 instruction.
-void GenerateSimpleTestCases(const string& outdir) {
-  DexBuilder dex_file;
-
-  ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.SimpleTests")};
-  cbuilder.set_source_file("dex_testcase_generator.cc#GenerateSimpleTestCases");
-
-  // int return5() { return 5; }
-  auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})};
-  {
-    LiveRegister r{return5.AllocRegister()};
-    return5.BuildConst4(r, 5);
-    return5.BuildReturn(r);
-  }
-  return5.Encode();
-
-  // int return5() { return 5; }
-  auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")};
-  auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})};
-  [&](MethodBuilder& method) {
-    LiveRegister five{method.AllocRegister()};
-    method.BuildConst4(five, 5);
-    LiveRegister object{method.AllocRegister()};
-    method.BuildNew(
-        object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five);
-    method.BuildReturn(object, /*is_object=*/true);
-  }(returnInteger5);
-  returnInteger5.Encode();
-
-  // // int returnParam(int x) { return x; }
-  auto returnParam{cbuilder.CreateMethod("returnParam",
-                                         Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
-  returnParam.BuildReturn(Value::Parameter(0));
-  returnParam.Encode();
-
-  // int returnStringLength(String x) { return x.length(); }
-  auto string_type{TypeDescriptor::FromClassname("java.lang.String")};
-  MethodDeclData string_length{
-      dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()})};
-
-  auto returnStringLength{
-      cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})};
-  {
-    LiveRegister result = returnStringLength.AllocRegister();
-    returnStringLength.AddInstruction(
-        Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
-    returnStringLength.BuildReturn(result);
-  }
-  returnStringLength.Encode();
-
-  // int returnIfZero(int x) { if (x == 0) { return 5; } else { return 3; } }
-  MethodBuilder returnIfZero{cbuilder.CreateMethod(
-      "returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
-  {
-    LiveRegister resultIfZero{returnIfZero.AllocRegister()};
-    Value else_target{returnIfZero.MakeLabel()};
-    returnIfZero.AddInstruction(Instruction::OpWithArgs(
-        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
-    // else branch
-    returnIfZero.BuildConst4(resultIfZero, 3);
-    returnIfZero.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero));
-    // then branch
-    returnIfZero.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
-    returnIfZero.BuildConst4(resultIfZero, 5);
-    returnIfZero.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero));
-  }
-  returnIfZero.Encode();
-
-  // int returnIfNotZero(int x) { if (x != 0) { return 5; } else { return 3; } }
-  MethodBuilder returnIfNotZero{cbuilder.CreateMethod(
-      "returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
-  {
-    LiveRegister resultIfNotZero{returnIfNotZero.AllocRegister()};
-    Value else_target{returnIfNotZero.MakeLabel()};
-    returnIfNotZero.AddInstruction(Instruction::OpWithArgs(
-        Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target));
-    // else branch
-    returnIfNotZero.BuildConst4(resultIfNotZero, 3);
-    returnIfNotZero.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
-    // then branch
-    returnIfNotZero.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
-    returnIfNotZero.BuildConst4(resultIfNotZero, 5);
-    returnIfNotZero.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
-  }
-  returnIfNotZero.Encode();
-
-  // Make sure backwards branches work too.
-  //
-  // Pseudo code for test:
-  // {
-  //   zero = 0;
-  //   result = 1;
-  //   if (zero == 0) goto B;
-  // A:
-  //   return result;
-  // B:
-  //   result = 2;
-  //   if (zero == 0) goto A;
-  //   result = 3;
-  //   return result;
-  // }
-  // If it runs correctly, this test should return 2.
-  MethodBuilder backwardsBranch{
-      cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})};
-  [](MethodBuilder& method) {
-    LiveRegister zero = method.AllocRegister();
-    LiveRegister result = method.AllocRegister();
-    Value labelA = method.MakeLabel();
-    Value labelB = method.MakeLabel();
-    method.BuildConst4(zero, 0);
-    method.BuildConst4(result, 1);
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelB));
-
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelA));
-    method.BuildReturn(result);
-
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelB));
-    method.BuildConst4(result, 2);
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelA));
-
-    method.BuildConst4(result, 3);
-    method.BuildReturn(result);
-  }(backwardsBranch);
-  backwardsBranch.Encode();
-
-  // Test that we can make a null value. Basically:
-  //
-  // public static String returnNull() { return null; }
-  MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
-  [](MethodBuilder& method) {
-    LiveRegister zero = method.AllocRegister();
-    method.BuildConst4(zero, 0);
-    method.BuildReturn(zero, /*is_object=*/true);
-  }(returnNull);
-  returnNull.Encode();
-
-  // Test that we can make String literals. Basically:
-  //
-  // public static String makeString() { return "Hello, World!"; }
-  MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
-  [](MethodBuilder& method) {
-    LiveRegister string = method.AllocRegister();
-    method.BuildConstString(string, "Hello, World!");
-    method.BuildReturn(string, /*is_object=*/true);
-  }(makeString);
-  makeString.Encode();
-
-  // Make sure strings are sorted correctly.
-  //
-  // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } }
-  MethodBuilder returnStringIfZeroAB{
-      cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
-  [&](MethodBuilder& method) {
-    LiveRegister resultIfZero{method.AllocRegister()};
-    Value else_target{method.MakeLabel()};
-    method.AddInstruction(Instruction::OpWithArgs(
-        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
-    // else branch
-    method.BuildConstString(resultIfZero, "b");
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
-    // then branch
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
-    method.BuildConstString(resultIfZero, "a");
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
-    method.Encode();
-  }(returnStringIfZeroAB);
-  // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } }
-  MethodBuilder returnStringIfZeroBA{
-      cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
-  [&](MethodBuilder& method) {
-    LiveRegister resultIfZero{method.AllocRegister()};
-    Value else_target{method.MakeLabel()};
-    method.AddInstruction(Instruction::OpWithArgs(
-        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
-    // else branch
-    method.BuildConstString(resultIfZero, "a");
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
-    // then branch
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
-    method.BuildConstString(resultIfZero, "b");
-    method.AddInstruction(
-        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
-    method.Encode();
-  }(returnStringIfZeroBA);
-
-  // Make sure we can invoke static methods that return an object
-  // String invokeStaticReturnObject(int n, int radix) { return java.lang.Integer.toString(n,
-  // radix); }
-  MethodBuilder invokeStaticReturnObject{
-      cbuilder.CreateMethod("invokeStaticReturnObject",
-                            Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
-  [&](MethodBuilder& method) {
-    LiveRegister result{method.AllocRegister()};
-    MethodDeclData to_string{dex_file.GetOrDeclareMethod(
-        TypeDescriptor::FromClassname("java.lang.Integer"),
-        "toString",
-        Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
-    method.AddInstruction(Instruction::InvokeStaticObject(
-        to_string.id, result, Value::Parameter(0), Value::Parameter(1)));
-    method.BuildReturn(result, /*is_object=*/true);
-    method.Encode();
-  }(invokeStaticReturnObject);
-
-  // Make sure we can invoke virtual methods that return an object
-  // String invokeVirtualReturnObject(String s, int n) { return s.substring(n); }
-  MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod(
-      "invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})};
-  [&](MethodBuilder& method) {
-    LiveRegister result{method.AllocRegister()};
-    MethodDeclData substring{dex_file.GetOrDeclareMethod(
-        string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})};
-    method.AddInstruction(Instruction::InvokeVirtualObject(
-        substring.id, result, Value::Parameter(0), Value::Parameter(1)));
-    method.BuildReturn(result, /*is_object=*/true);
-    method.Encode();
-  }(invokeVirtualReturnObject);
-
-  // Make sure we can cast objects
-  // String castObjectToString(Object o) { return (String)o; }
-  MethodBuilder castObjectToString{cbuilder.CreateMethod(
-      "castObjectToString",
-      Prototype{string_type, TypeDescriptor::FromClassname("java.lang.Object")})};
-  [&](MethodBuilder& method) {
-    const ir::Type* type_def = dex_file.GetOrAddType(string_type.descriptor());
-    method.AddInstruction(
-        Instruction::Cast(Value::Parameter(0), Value::Type(type_def->orig_index)));
-    method.BuildReturn(Value::Parameter(0), /*is_object=*/true);
-    method.Encode();
-  }(castObjectToString);
-
-  TypeDescriptor test_class = TypeDescriptor::FromClassname("android.startop.test.TestClass");
-
-  // Read a static field
-  // int readStaticField() { return TestClass.staticInteger; }
-  MethodBuilder readStaticField{
-      cbuilder.CreateMethod("readStaticField", Prototype{TypeDescriptor::Int()})};
-  [&](MethodBuilder& method) {
-    const ir::FieldDecl* field =
-        dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
-    LiveRegister result{method.AllocRegister()};
-    method.AddInstruction(Instruction::GetStaticField(field->orig_index, result));
-    method.BuildReturn(result, /*is_object=*/false);
-    method.Encode();
-  }(readStaticField);
-
-  // Set a static field
-  // void setStaticField() { TestClass.staticInteger = 7; }
-  MethodBuilder setStaticField{
-      cbuilder.CreateMethod("setStaticField", Prototype{TypeDescriptor::Void()})};
-  [&](MethodBuilder& method) {
-    const ir::FieldDecl* field =
-        dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
-    LiveRegister number{method.AllocRegister()};
-    method.BuildConst4(number, 7);
-    method.AddInstruction(Instruction::SetStaticField(field->orig_index, number));
-    method.BuildReturn();
-    method.Encode();
-  }(setStaticField);
-
-  // Read an instance field
-  // int readInstanceField(TestClass obj) { return obj.instanceField; }
-  MethodBuilder readInstanceField{
-      cbuilder.CreateMethod("readInstanceField", Prototype{TypeDescriptor::Int(), test_class})};
-  [&](MethodBuilder& method) {
-    const ir::FieldDecl* field =
-        dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
-    LiveRegister result{method.AllocRegister()};
-    method.AddInstruction(Instruction::GetField(field->orig_index, result, Value::Parameter(0)));
-    method.BuildReturn(result, /*is_object=*/false);
-    method.Encode();
-  }(readInstanceField);
-
-  // Set an instance field
-  // void setInstanceField(TestClass obj) { obj.instanceField = 7; }
-  MethodBuilder setInstanceField{
-      cbuilder.CreateMethod("setInstanceField", Prototype{TypeDescriptor::Void(), test_class})};
-  [&](MethodBuilder& method) {
-    const ir::FieldDecl* field =
-        dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
-    LiveRegister number{method.AllocRegister()};
-    method.BuildConst4(number, 7);
-    method.AddInstruction(Instruction::SetField(field->orig_index, Value::Parameter(0), number));
-    method.BuildReturn();
-    method.Encode();
-  }(setInstanceField);
-
-  slicer::MemView image{dex_file.CreateImage()};
-  std::ofstream out_file(outdir + "/simple.dex");
-  out_file.write(image.ptr<const char>(), image.size());
-}
-
-int main(int argc, char** argv) {
-  CHECK_EQ(argc, 2);
-
-  string outdir = argv[1];
-
-  GenerateTrivialDexFile(outdir);
-  GenerateSimpleTestCases(outdir);
-}
diff --git a/startop/view_compiler/java_lang_builder.cc b/startop/view_compiler/java_lang_builder.cc
deleted file mode 100644
index 920caee..0000000
--- a/startop/view_compiler/java_lang_builder.cc
+++ /dev/null
@@ -1,115 +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.
- */
-
-#include "java_lang_builder.h"
-
-#include "android-base/stringprintf.h"
-
-using android::base::StringPrintf;
-using std::string;
-
-void JavaLangViewBuilder::Start() const {
-  out_ << StringPrintf("package %s;\n", package_.c_str())
-       << "import android.content.Context;\n"
-          "import android.content.res.Resources;\n"
-          "import android.content.res.XmlResourceParser;\n"
-          "import android.util.AttributeSet;\n"
-          "import android.util.Xml;\n"
-          "import android.view.*;\n"
-          "import android.widget.*;\n"
-          "\n"
-          "public final class CompiledView {\n"
-          "\n"
-          "static <T extends View> T createView(Context context, AttributeSet attrs, View parent, "
-          "String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {"
-          "\n"
-          "  if (factory2 != null) {\n"
-          "    return (T)factory2.onCreateView(parent, name, context, attrs);\n"
-          "  } else if (factory != null) {\n"
-          "    return (T)factory.onCreateView(name, context, attrs);\n"
-          "  }\n"
-          // TODO: find a way to call the private factory
-          "  return null;\n"
-          "}\n"
-          "\n"
-          "  public static View inflate(Context context) {\n"
-          "    try {\n"
-          "      LayoutInflater inflater = LayoutInflater.from(context);\n"
-          "      LayoutInflater.Factory factory = inflater.getFactory();\n"
-          "      LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n"
-          "      Resources res = context.getResources();\n"
-       << StringPrintf("      XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n",
-                       package_.c_str(),
-                       layout_name_.c_str())
-       << "      AttributeSet attrs = Xml.asAttributeSet(xml);\n"
-          // The Java-language XmlPullParser needs a call to next to find the start document tag.
-          "      xml.next(); // start document\n";
-}
-
-void JavaLangViewBuilder::Finish() const {
-  out_ << "    } catch (Exception e) {\n"
-          "      return null;\n"
-          "    }\n"  // end try
-          "  }\n"    // end inflate
-          "}\n";     // end CompiledView
-}
-
-void JavaLangViewBuilder::StartView(const string& class_name, bool /*is_viewgroup*/) {
-  const string view_var = MakeVar("view");
-  const string layout_var = MakeVar("layout");
-  std::string parent = "null";
-  if (!view_stack_.empty()) {
-    const StackEntry& parent_entry = view_stack_.back();
-    parent = parent_entry.view_var;
-  }
-  out_ << "      xml.next(); // <" << class_name << ">\n"
-       << StringPrintf("      %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n",
-                       class_name.c_str(),
-                       view_var.c_str(),
-                       parent.c_str(),
-                       class_name.c_str())
-       << StringPrintf("      if (%s == null) %s = new %s(context, attrs);\n",
-                       view_var.c_str(),
-                       view_var.c_str(),
-                       class_name.c_str());
-  if (!view_stack_.empty()) {
-    out_ << StringPrintf("      ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n",
-                         layout_var.c_str(),
-                         parent.c_str());
-  }
-  view_stack_.push_back({class_name, view_var, layout_var});
-}
-
-void JavaLangViewBuilder::FinishView() {
-  const StackEntry var = view_stack_.back();
-  view_stack_.pop_back();
-  if (!view_stack_.empty()) {
-    const string& parent = view_stack_.back().view_var;
-    out_ << StringPrintf("      xml.next(); // </%s>\n", var.class_name.c_str())
-         << StringPrintf("      %s.addView(%s, %s);\n",
-                         parent.c_str(),
-                         var.view_var.c_str(),
-                         var.layout_params_var.c_str());
-  } else {
-    out_ << StringPrintf("      return %s;\n", var.view_var.c_str());
-  }
-}
-
-const std::string JavaLangViewBuilder::MakeVar(std::string prefix) {
-  std::stringstream v;
-  v << prefix << view_id_++;
-  return v.str();
-}
diff --git a/startop/view_compiler/java_lang_builder.h b/startop/view_compiler/java_lang_builder.h
deleted file mode 100644
index 69356d3..0000000
--- a/startop/view_compiler/java_lang_builder.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef JAVA_LANG_BUILDER_H_
-#define JAVA_LANG_BUILDER_H_
-
-#include <iostream>
-#include <sstream>
-#include <vector>
-
-// Build Java language code to instantiate views.
-//
-// This has a very small interface to make it easier to generate additional
-// backends, such as a direct-to-DEX version.
-class JavaLangViewBuilder {
- public:
-  JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout)
-      : package_(package), layout_name_(layout_name), out_(out) {}
-
-  // Begin generating a class. Adds the package boilerplate, etc.
-  void Start() const;
-  // Finish generating a class, closing off any open curly braces, etc.
-  void Finish() const;
-
-  // Begin creating a view (i.e. process the opening tag)
-  void StartView(const std::string& class_name, bool is_viewgroup);
-  // Finish a view, after all of its child nodes have been processed.
-  void FinishView();
-
- private:
-  const std::string MakeVar(std::string prefix);
-
-  std::string const package_;
-  std::string const layout_name_;
-
-  std::ostream& out_;
-
-  size_t view_id_ = 0;
-
-  struct StackEntry {
-      // The class name for this view object
-      const std::string class_name;
-
-      // The variable name that is holding the view object
-      const std::string view_var;
-
-      // The variable name that holds the object's layout parameters
-      const std::string layout_params_var;
-  };
-  std::vector<StackEntry> view_stack_;
-};
-
-#endif  // JAVA_LANG_BUILDER_H_
diff --git a/startop/view_compiler/layout_validation.cc b/startop/view_compiler/layout_validation.cc
deleted file mode 100644
index 8c77377..0000000
--- a/startop/view_compiler/layout_validation.cc
+++ /dev/null
@@ -1,42 +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.
- */
-
-#include "layout_validation.h"
-
-#include "android-base/stringprintf.h"
-
-namespace startop {
-
-void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
-  if (0 == name.compare(u"merge")) {
-    message_ = "Merge tags are not supported";
-    can_compile_ = false;
-  }
-  if (0 == name.compare(u"include")) {
-    message_ = "Include tags are not supported";
-    can_compile_ = false;
-  }
-  if (0 == name.compare(u"view")) {
-    message_ = "View tags are not supported";
-    can_compile_ = false;
-  }
-  if (0 == name.compare(u"fragment")) {
-    message_ = "Fragment tags are not supported";
-    can_compile_ = false;
-  }
-}
-
-}  // namespace startop
\ No newline at end of file
diff --git a/startop/view_compiler/layout_validation.h b/startop/view_compiler/layout_validation.h
deleted file mode 100644
index bed34bb..0000000
--- a/startop/view_compiler/layout_validation.h
+++ /dev/null
@@ -1,46 +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.
- */
-
-#ifndef LAYOUT_VALIDATION_H_
-#define LAYOUT_VALIDATION_H_
-
-#include "dex_builder.h"
-
-#include <string>
-
-namespace startop {
-
-// This visitor determines whether a layout can be compiled. Since we do not currently support all
-// features, such as includes and merges, we need to pre-validate the layout before we start
-// compiling.
-class LayoutValidationVisitor {
- public:
-  void VisitStartDocument() const {}
-  void VisitEndDocument() const {}
-  void VisitStartTag(const std::u16string& name);
-  void VisitEndTag() const {}
-
-  const std::string& message() const { return message_; }
-  bool can_compile() const { return can_compile_; }
-
- private:
-  std::string message_{"Okay"};
-  bool can_compile_{true};
-};
-
-}  // namespace startop
-
-#endif  // LAYOUT_VALIDATION_H_
diff --git a/startop/view_compiler/layout_validation_test.cc b/startop/view_compiler/layout_validation_test.cc
deleted file mode 100644
index b74cdae..0000000
--- a/startop/view_compiler/layout_validation_test.cc
+++ /dev/null
@@ -1,163 +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.
- */
-#include "tinyxml_layout_parser.h"
-
-#include "gtest/gtest.h"
-
-using startop::CanCompileLayout;
-using std::string;
-
-namespace {
-void ValidateXmlText(const string& xml, bool expected) {
-  tinyxml2::XMLDocument doc;
-  doc.Parse(xml.c_str());
-  EXPECT_EQ(CanCompileLayout(doc), expected);
-}
-}  // namespace
-
-TEST(LayoutValidationTest, SingleButtonLayout) {
-  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
-<Button xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:text="Hello, World!">
-
-</Button>)";
-  ValidateXmlText(xml, /*expected=*/true);
-}
-
-TEST(LayoutValidationTest, SmallConstraintLayout) {
-  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <Button
-        android:id="@+id/button6"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="16dp"
-        android:layout_marginBottom="16dp"
-        android:text="Button"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent" />
-
-    <Button
-        android:id="@+id/button7"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="16dp"
-        android:text="Button2"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/button6" />
-
-    <Button
-        android:id="@+id/button8"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="16dp"
-        android:text="Button1"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/button7" />
-</android.support.constraint.ConstraintLayout>)";
-  ValidateXmlText(xml, /*expected=*/true);
-}
-
-TEST(LayoutValidationTest, MergeNode) {
-  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <TextView
-        android:id="@+id/textView3"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="TextView" />
-
-    <Button
-        android:id="@+id/button9"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Button" />
-</merge>)";
-  ValidateXmlText(xml, /*expected=*/false);
-}
-
-TEST(LayoutValidationTest, IncludeLayout) {
-  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <include
-        layout="@layout/single_button_layout"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>)";
-  ValidateXmlText(xml, /*expected=*/false);
-}
-
-TEST(LayoutValidationTest, ViewNode) {
-  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <view
-        class="android.support.design.button.MaterialButton"
-        id="@+id/view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>)";
-  ValidateXmlText(xml, /*expected=*/false);
-}
-
-TEST(LayoutValidationTest, FragmentNode) {
-  // This test case is from https://developer.android.com/guide/components/fragments
-  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <fragment android:name="com.example.news.ArticleListFragment"
-            android:id="@+id/list"
-            android:layout_weight="1"
-            android:layout_width="0dp"
-            android:layout_height="match_parent" />
-    <fragment android:name="com.example.news.ArticleReaderFragment"
-            android:id="@+id/viewer"
-            android:layout_weight="2"
-            android:layout_width="0dp"
-            android:layout_height="match_parent" />
-</LinearLayout>)";
-  ValidateXmlText(xml, /*expected=*/false);
-}
diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc
deleted file mode 100644
index 11ecde2..0000000
--- a/startop/view_compiler/main.cc
+++ /dev/null
@@ -1,172 +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.
- */
-
-#include "gflags/gflags.h"
-
-#include "android-base/stringprintf.h"
-#include "apk_layout_compiler.h"
-#include "dex_builder.h"
-#include "dex_layout_compiler.h"
-#include "java_lang_builder.h"
-#include "layout_validation.h"
-#include "tinyxml_layout_parser.h"
-#include "util.h"
-
-#include "tinyxml2.h"
-
-#include <fstream>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-namespace {
-
-using namespace tinyxml2;
-using android::base::StringPrintf;
-using startop::dex::ClassBuilder;
-using startop::dex::DexBuilder;
-using startop::dex::MethodBuilder;
-using startop::dex::Prototype;
-using startop::dex::TypeDescriptor;
-using namespace startop::util;
-using std::string;
-
-constexpr char kStdoutFilename[]{"stdout"};
-
-DEFINE_bool(apk, false, "Compile layouts in an APK");
-DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
-DEFINE_int32(infd, -1, "Read input from the given file descriptor");
-DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
-DEFINE_string(package, "", "The package name for the generated class (required)");
-
-template <typename Visitor>
-class XmlVisitorAdapter : public XMLVisitor {
- public:
-  explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
-
-  bool VisitEnter(const XMLDocument& /*doc*/) override {
-    visitor_->VisitStartDocument();
-    return true;
-  }
-
-  bool VisitExit(const XMLDocument& /*doc*/) override {
-    visitor_->VisitEndDocument();
-    return true;
-  }
-
-  bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override {
-    visitor_->VisitStartTag(
-        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
-            element.Name()));
-    return true;
-  }
-
-  bool VisitExit(const XMLElement& /*element*/) override {
-    visitor_->VisitEndTag();
-    return true;
-  }
-
- private:
-  Visitor* visitor_;
-};
-
-template <typename Builder>
-void CompileLayout(XMLDocument* xml, Builder* builder) {
-  startop::LayoutCompilerVisitor visitor{builder};
-  XmlVisitorAdapter<decltype(visitor)> adapter{&visitor};
-  xml->Accept(&adapter);
-}
-
-}  // end namespace
-
-int main(int argc, char** argv) {
-  constexpr size_t kProgramName = 0;
-  constexpr size_t kFileNameParam = 1;
-  constexpr size_t kNumRequiredArgs = 1;
-
-  gflags::SetUsageMessage(
-      "Compile XML layout files into equivalent Java language code\n"
-      "\n"
-      "  example usage:  viewcompiler layout.xml --package com.example.androidapp");
-  gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true);
-
-  gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package");
-  if (argc < kNumRequiredArgs || cmd.is_default) {
-    gflags::ShowUsageWithFlags(argv[kProgramName]);
-    return 1;
-  }
-
-  const bool is_stdout = FLAGS_out == kStdoutFilename;
-
-  std::ofstream outfile;
-  if (!is_stdout) {
-    outfile.open(FLAGS_out);
-  }
-
-  if (FLAGS_apk) {
-    const startop::CompilationTarget target =
-        FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage;
-    if (FLAGS_infd >= 0) {
-      startop::CompileApkLayoutsFd(
-          android::base::unique_fd{FLAGS_infd}, target, is_stdout ? std::cout : outfile);
-    } else {
-      if (argc < 2) {
-        gflags::ShowUsageWithFlags(argv[kProgramName]);
-        return 1;
-      }
-      const char* const filename = argv[kFileNameParam];
-      startop::CompileApkLayouts(filename, target, is_stdout ? std::cout : outfile);
-    }
-    return 0;
-  }
-
-  const char* const filename = argv[kFileNameParam];
-  const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
-
-  XMLDocument xml;
-  xml.LoadFile(filename);
-
-  string message{};
-  if (!startop::CanCompileLayout(xml, &message)) {
-    LOG(ERROR) << "Layout not supported: " << message;
-    return 1;
-  }
-
-  if (FLAGS_dex) {
-    DexBuilder dex_file;
-    string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());
-    ClassBuilder compiled_view{dex_file.MakeClass(class_name)};
-    MethodBuilder method{compiled_view.CreateMethod(
-        layout_name,
-        Prototype{TypeDescriptor::FromClassname("android.view.View"),
-                  TypeDescriptor::FromClassname("android.content.Context"),
-                  TypeDescriptor::Int()})};
-    startop::DexViewBuilder builder{&method};
-    CompileLayout(&xml, &builder);
-    method.Encode();
-
-    slicer::MemView image{dex_file.CreateImage()};
-
-    (is_stdout ? std::cout : outfile).write(image.ptr<const char>(), image.size());
-  } else {
-    // Generate Java language output.
-    JavaLangViewBuilder builder{FLAGS_package, layout_name, is_stdout ? std::cout : outfile};
-
-    CompileLayout(&xml, &builder);
-  }
-  return 0;
-}
diff --git a/startop/view_compiler/tinyxml_layout_parser.cc b/startop/view_compiler/tinyxml_layout_parser.cc
deleted file mode 100644
index 1b3a81f..0000000
--- a/startop/view_compiler/tinyxml_layout_parser.cc
+++ /dev/null
@@ -1,34 +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.
- */
-#include "tinyxml_layout_parser.h"
-
-#include "layout_validation.h"
-
-namespace startop {
-
-bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message) {
-  LayoutValidationVisitor validator;
-  TinyXmlVisitorAdapter adapter{&validator};
-  xml.Accept(&adapter);
-
-  if (message != nullptr) {
-    *message = validator.message();
-  }
-
-  return validator.can_compile();
-}
-
-}  // namespace startop
diff --git a/startop/view_compiler/tinyxml_layout_parser.h b/startop/view_compiler/tinyxml_layout_parser.h
deleted file mode 100644
index 8f714a2..0000000
--- a/startop/view_compiler/tinyxml_layout_parser.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef TINYXML_LAYOUT_PARSER_H_
-#define TINYXML_LAYOUT_PARSER_H_
-
-#include "tinyxml2.h"
-
-#include <codecvt>
-#include <locale>
-#include <string>
-
-namespace startop {
-
-template <typename Visitor>
-class TinyXmlVisitorAdapter : public tinyxml2::XMLVisitor {
- public:
-  explicit TinyXmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
-
-  bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/) override {
-    visitor_->VisitStartDocument();
-    return true;
-  }
-
-  bool VisitExit(const tinyxml2::XMLDocument& /*doc*/) override {
-    visitor_->VisitEndDocument();
-    return true;
-  }
-
-  bool VisitEnter(const tinyxml2::XMLElement& element,
-                  const tinyxml2::XMLAttribute* /*firstAttribute*/) override {
-    visitor_->VisitStartTag(
-        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
-            element.Name()));
-    return true;
-  }
-
-  bool VisitExit(const tinyxml2::XMLElement& /*element*/) override {
-    visitor_->VisitEndTag();
-    return true;
-  }
-
- private:
-  Visitor* visitor_;
-};
-
-// Returns whether a layout resource represented by a TinyXML document is supported by the layout
-// compiler.
-bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message = nullptr);
-
-}  // namespace startop
-
-#endif  // TINYXML_LAYOUT_PARSER_H_
diff --git a/startop/view_compiler/util.cc b/startop/view_compiler/util.cc
deleted file mode 100644
index c34d7b0..0000000
--- a/startop/view_compiler/util.cc
+++ /dev/null
@@ -1,38 +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.
- */
-
-#include "util.h"
-
-using std::string;
-
-namespace startop {
-namespace util {
-
-// TODO: see if we can borrow this from somewhere else, like aapt2.
-string FindLayoutNameFromFilename(const string& filename) {
-  size_t start = filename.rfind('/');
-  if (start == string::npos) {
-    start = 0;
-  } else {
-    start++;  // advance past '/' character
-  }
-  size_t end = filename.find('.', start);
-
-  return filename.substr(start, end - start);
-}
-
-}  // namespace util
-}  // namespace startop
diff --git a/startop/view_compiler/util.h b/startop/view_compiler/util.h
deleted file mode 100644
index 0176175..0000000
--- a/startop/view_compiler/util.h
+++ /dev/null
@@ -1,29 +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.
- */
-#ifndef VIEW_COMPILER_UTIL_H_
-#define VIEW_COMPILER_UTIL_H_
-
-#include <string>
-
-namespace startop {
-namespace util {
-
-std::string FindLayoutNameFromFilename(const std::string& filename);
-
-}  // namespace util
-}  // namespace startop
-
-#endif  // VIEW_COMPILER_UTIL_H_
diff --git a/startop/view_compiler/util_test.cc b/startop/view_compiler/util_test.cc
deleted file mode 100644
index 50682a0..0000000
--- a/startop/view_compiler/util_test.cc
+++ /dev/null
@@ -1,34 +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.
- */
-
-#include "util.h"
-
-#include "gtest/gtest.h"
-
-using std::string;
-
-namespace startop {
-namespace util {
-
-TEST(UtilTest, FindLayoutNameFromFilename) {
-  EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("foo/bar.xml"));
-  EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("bar.xml"));
-  EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("./foo/bar.xml"));
-  EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("/foo/bar.xml"));
-}
-
-}  // namespace util
-}  // namespace startop
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a7675d6..def52a5 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -942,6 +942,7 @@
          * @return the Telecom identifier associated with this {@link Call} . This is not a stable
          * identifier and is not guaranteed to be unique across device reboots.
          */
+        @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES)
         public @NonNull String getId() { return mTelecomCallId; }
 
         /** {@hide} */
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
index 29f436d..ad1b6f9 100644
--- a/telecomm/java/android/telecom/StreamingCall.java
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -25,6 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.server.telecom.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -57,6 +60,7 @@
     /**
      * The ID associated with this call.  This is the same value as {@link CallControl#getCallId()}.
      */
+    @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES)
     public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
 
     /**
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 971fc78..e20e4d2 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -202,6 +202,24 @@
                     && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
         }
 
+        private static String barringTypeToString(@BarringType int barringType) {
+            return switch (barringType) {
+                case BARRING_TYPE_NONE -> "NONE";
+                case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL";
+                case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL";
+                case BARRING_TYPE_UNKNOWN -> "UNKNOWN";
+                default -> "UNKNOWN(" + barringType + ")";
+            };
+        }
+
+        @Override
+        public String toString() {
+            return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType)
+                    + ", mIsConditionallyBarred=" + mIsConditionallyBarred
+                    + ", mConditionalBarringFactor=" + mConditionalBarringFactor
+                    + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}";
+        }
+
         /** @hide */
         public BarringServiceInfo(Parcel p) {
             mBarringType = p.readInt();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 63e91ad..042b2a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1056,6 +1056,14 @@
             "carrier_use_ims_first_for_emergency_bool";
 
     /**
+     * When {@code true}, this carrier will preferentially dial normal routed emergency calls over
+     * an in-service SIM if one is available.
+     * @hide
+     */
+    public static final String KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL =
+            "prefer_in_service_sim_for_normal_routed_emergency_calls_bool";
+
+    /**
      * When {@code true}, the determination of whether to place a call as an emergency call will be
      * based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which
      * the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers
@@ -3140,6 +3148,14 @@
     public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = "roaming_operator_string_array";
 
     /**
+     * Config to show the roaming indicator (i.e. the "R" icon) from the status bar when roaming.
+     * The roaming indicator will be shown if this is {@code true} and will not be shown if this is
+     * {@code false}.
+     */
+    @FlaggedApi(Flags.FLAG_HIDE_ROAMING_ICON)
+    public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool";
+
+    /**
      * URL from which the proto containing the public key of the Carrier used for
      * IMSI encryption will be downloaded.
      * @hide
@@ -3305,11 +3321,11 @@
      * If {@code false} the SPN display checks if the current MCC/MNC is different from the
      * SIM card's MCC/MNC.
      *
-     * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
-     * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
-     * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
-     * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
-     * @see KEY_FORCE_HOME_NETWORK_BOOL
+     * @see #KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+     * @see #KEY_ROAMING_OPERATOR_STRING_ARRAY
+     * @see #KEY_FORCE_HOME_NETWORK_BOOL
      *
      * @hide
      */
@@ -8790,7 +8806,9 @@
          * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_NONE},
          * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1024_BIT_MODP},
          * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1536_BIT_MODP},
-         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP}
+         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP},
+         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_3072_BIT_MODP},
+         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_4096_BIT_MODP}
          */
         public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY =
                 KEY_PREFIX + "diffie_hellman_groups_int_array";
@@ -8858,7 +8876,8 @@
 
         /**
          * List of supported encryption algorithms for child session. Possible values are
-         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC},
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR}
          */
         public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY =
                 KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array";
@@ -9673,7 +9692,6 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
-     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -9874,6 +9892,8 @@
         sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
+        sDefaults.putBoolean(KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL,
+                false);
         sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
         sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
         sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
@@ -10184,6 +10204,7 @@
                 false);
         sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true);
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false);
         sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true);
@@ -10523,6 +10544,8 @@
         auto_data_switch_rat_signal_score_string_bundle.putIntArray(
                 "LTE", new int[]{3731, 5965, 8618, 11179, 13384});
         auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
                 "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
         auto_data_switch_rat_signal_score_string_bundle.putIntArray(
                 "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index f9844bc..a8c077d 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -16,9 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * Describes the cause of a disconnected call. Those disconnect causes can be converted into a more
  * generic {@link android.telecom.DisconnectCause} object.
@@ -363,6 +366,7 @@
     /**
      * Indicates that the call was unable to be made because the satellite modem is enabled.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_ENABLED = 82;
 
     //*********************************************************************************************
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index cb4a6e7..7ccc27e 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +31,8 @@
 import android.telephony.Annotation.NetworkType;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -206,6 +209,7 @@
     /**
      * MMS service
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final int SERVICE_TYPE_MMS = 6;
 
     /** @hide  */
@@ -702,6 +706,7 @@
      *
      * @return {@code true} if network is a non-terrestrial network else {@code false}.
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public boolean isNonTerrestrialNetwork() {
         return mIsNonTerrestrialNetwork;
     }
@@ -1186,6 +1191,7 @@
          *                                            else {@code false}.
          * @return The builder.
          */
+        @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
         public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
             mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
             return this;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index cee2efb..f161f31 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,6 +44,7 @@
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -2957,7 +2959,8 @@
      * @param number  The phone number used for WPS call.
      * @return {@code true} if number matches WPS pattern and {@code false} otherwise.
      */
-    public static boolean isWpsCallNumber(@Nullable String number) {
+    @FlaggedApi(Flags.FLAG_ENABLE_WPS_CHECK_API_FLAG)
+    public static boolean isWpsCallNumber(@NonNull String number) {
         return (number != null) && (number.startsWith(PREFIX_WPS)
                 || number.startsWith(PREFIX_WPS_CLIR_ACTIVATE)
                 || number.startsWith(PREFIX_WPS_CLIR_DEACTIVATE));
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5b8848c..85a85c6 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,6 +36,7 @@
 import android.telephony.NetworkRegistrationInfo.NRState;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -2256,6 +2258,7 @@
      *
      * @return {@code true} if device is connected to a non-terrestrial network else {@code false}.
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public boolean isUsingNonTerrestrialNetwork() {
         synchronized (mNetworkRegistrationInfos) {
             for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 548fa97..c0d6b30 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.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
@@ -128,6 +129,7 @@
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.io.IOException;
@@ -1195,6 +1197,7 @@
      * The dialer app receives this event via
      * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final String EVENT_DISPLAY_SOS_MESSAGE =
             "android.telephony.event.DISPLAY_SOS_MESSAGE";
 
@@ -15030,14 +15033,6 @@
     @TestApi
     public static final int HAL_SERVICE_IMS = 7;
 
-    /**
-     * HAL service type that supports the HAL APIs implementation of IRadioSatellite
-     * {@link RadioSatelliteProxy}
-     * @hide
-     */
-    @TestApi
-    public static final int HAL_SERVICE_SATELLITE = 8;
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"HAL_SERVICE_"},
@@ -15050,7 +15045,6 @@
                     HAL_SERVICE_SIM,
                     HAL_SERVICE_VOICE,
                     HAL_SERVICE_IMS,
-                    HAL_SERVICE_SATELLITE
             })
     public @interface HalService {}
 
@@ -17489,6 +17483,8 @@
      * {@link CarrierConfigManager
      * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
      * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+     *
+     * @hide
      */
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
 
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index e9af486..11cbcb1 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -15,6 +15,7 @@
  */
 package android.telephony.data;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,6 +36,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -121,6 +123,9 @@
     public static final int TYPE_BIP = ApnTypes.BIP;
     /** APN type for ENTERPRISE. */
     public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
+    /** APN type for RCS (Rich Communication Services). */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final int TYPE_RCS = ApnTypes.RCS;
 
     /** @hide */
     @IntDef(flag = true, prefix = {"TYPE_"}, value = {
@@ -139,6 +144,7 @@
             TYPE_BIP,
             TYPE_VSIM,
             TYPE_ENTERPRISE,
+            TYPE_RCS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApnType {
@@ -356,6 +362,17 @@
     @SystemApi
     public static final String TYPE_ENTERPRISE_STRING = "enterprise";
 
+    /**
+     * APN type for RCS (Rich Communication Services)
+     *
+     * Note: String representations of APN types are intended for system apps to communicate with
+     * modem components or carriers. Non-system apps should use the integer variants instead.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    public static final String TYPE_RCS_STRING = "rcs";
+
 
     /** @hide */
     @IntDef(prefix = { "AUTH_TYPE_" }, value = {
@@ -424,6 +441,26 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface MvnoType {}
 
+    /**
+     * Indicating this APN can be used when the device is using terrestrial cellular networks.
+     * @hide
+     */
+    public static final int INFRASTRUCTURE_CELLULAR = 1 << 0;
+
+    /**
+     * Indicating this APN can be used when the device is attached to satellites.
+     * @hide
+     */
+    public static final int INFRASTRUCTURE_SATELLITE = 1 << 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "INFRASTRUCTURE_" }, value = {
+            INFRASTRUCTURE_CELLULAR,
+            INFRASTRUCTURE_SATELLITE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InfrastructureBitmask {}
+
     private static final Map<String, Integer> APN_TYPE_STRING_MAP;
     private static final Map<Integer, String> APN_TYPE_INT_MAP;
     private static final Map<String, Integer> PROTOCOL_STRING_MAP;
@@ -449,6 +486,7 @@
         APN_TYPE_STRING_MAP.put(TYPE_ENTERPRISE_STRING, TYPE_ENTERPRISE);
         APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM);
         APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP);
+        APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);
 
         APN_TYPE_INT_MAP = new ArrayMap<>();
         APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
@@ -466,6 +504,7 @@
         APN_TYPE_INT_MAP.put(TYPE_ENTERPRISE, TYPE_ENTERPRISE_STRING);
         APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING);
         APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);
 
         PROTOCOL_STRING_MAP = new ArrayMap<>();
         PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -528,6 +567,7 @@
     private final int mCarrierId;
     private final int mSkip464Xlat;
     private final boolean mAlwaysOn;
+    private final @InfrastructureBitmask int mInfrastructureBitmask;
 
     /**
      * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
@@ -916,6 +956,29 @@
         return mAlwaysOn;
     }
 
+    /**
+     * Check if this APN can be used when the device is using certain infrastructure(s).
+     *
+     * @param infrastructures The infrastructure(s) the device is using.
+     *
+     * @return {@code true} if this APN can be used.
+     * @hide
+     */
+    public boolean isForInfrastructure(@InfrastructureBitmask int infrastructures) {
+        return (mInfrastructureBitmask & infrastructures) != 0;
+    }
+
+    /**
+     * @return The infrastructure bitmask of which the APN can be used on. For example, some APNs
+     * can only be used when the device is on cellular, on satellite, or both.
+     *
+     * @hide
+     */
+    @InfrastructureBitmask
+    public int getInfrastructureBitmask() {
+        return mInfrastructureBitmask;
+    }
+
     private ApnSetting(Builder builder) {
         this.mEntryName = builder.mEntryName;
         this.mApnName = builder.mApnName;
@@ -952,6 +1015,7 @@
         this.mCarrierId = builder.mCarrierId;
         this.mSkip464Xlat = builder.mSkip464Xlat;
         this.mAlwaysOn = builder.mAlwaysOn;
+        this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
     }
 
     /**
@@ -1031,6 +1095,8 @@
                         cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
                 .setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
                 .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
+                .setInfrastructureBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.INFRASTRUCTURE_BITMASK)))
                 .buildWithoutCheck();
     }
 
@@ -1070,6 +1136,7 @@
                 .setCarrierId(apn.mCarrierId)
                 .setSkip464Xlat(apn.mSkip464Xlat)
                 .setAlwaysOn(apn.mAlwaysOn)
+                .setInfrastructureBitmask(apn.mInfrastructureBitmask)
                 .buildWithoutCheck();
     }
 
@@ -1115,6 +1182,7 @@
         sb.append(", ").append(mCarrierId);
         sb.append(", ").append(mSkip464Xlat);
         sb.append(", ").append(mAlwaysOn);
+        sb.append(", ").append(mInfrastructureBitmask);
         sb.append(", ").append(Objects.hash(mUser, mPassword));
         return sb.toString();
     }
@@ -1179,7 +1247,7 @@
                 mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
                 mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
                 mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
-                mAlwaysOn);
+                mAlwaysOn, mInfrastructureBitmask);
     }
 
     @Override
@@ -1191,36 +1259,37 @@
         ApnSetting other = (ApnSetting) o;
 
         return mEntryName.equals(other.mEntryName)
-                && Objects.equals(mId, other.mId)
+                && mId == other.mId
                 && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
                 && Objects.equals(mApnName, other.mApnName)
                 && Objects.equals(mProxyAddress, other.mProxyAddress)
                 && Objects.equals(mMmsc, other.mMmsc)
                 && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
-                && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
-                && Objects.equals(mProxyPort, other.mProxyPort)
+                && mMmsProxyPort == other.mMmsProxyPort
+                && mProxyPort == other.mProxyPort
                 && Objects.equals(mUser, other.mUser)
                 && Objects.equals(mPassword, other.mPassword)
-                && Objects.equals(mAuthType, other.mAuthType)
-                && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
-                && Objects.equals(mProtocol, other.mProtocol)
-                && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
-                && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
-                && Objects.equals(mProfileId, other.mProfileId)
-                && Objects.equals(mPersistent, other.mPersistent)
-                && Objects.equals(mMaxConns, other.mMaxConns)
-                && Objects.equals(mWaitTime, other.mWaitTime)
-                && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
-                && Objects.equals(mMtuV4, other.mMtuV4)
-                && Objects.equals(mMtuV6, other.mMtuV6)
-                && Objects.equals(mMvnoType, other.mMvnoType)
+                && mAuthType == other.mAuthType
+                && mApnTypeBitmask == other.mApnTypeBitmask
+                && mProtocol == other.mProtocol
+                && mRoamingProtocol == other.mRoamingProtocol
+                && mCarrierEnabled == other.mCarrierEnabled
+                && mProfileId == other.mProfileId
+                && mPersistent == other.mPersistent
+                && mMaxConns == other.mMaxConns
+                && mWaitTime == other.mWaitTime
+                && mMaxConnsTime == other.mMaxConnsTime
+                && mMtuV4 == other.mMtuV4
+                && mMtuV6 == other.mMtuV6
+                && mMvnoType == other.mMvnoType
                 && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
-                && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
-                && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
-                && Objects.equals(mApnSetId, other.mApnSetId)
-                && Objects.equals(mCarrierId, other.mCarrierId)
-                && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
-                && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+                && mNetworkTypeBitmask == other.mNetworkTypeBitmask
+                && mLingeringNetworkTypeBitmask == other.mLingeringNetworkTypeBitmask
+                && mApnSetId == other.mApnSetId
+                && mCarrierId == other.mCarrierId
+                && mSkip464Xlat == other.mSkip464Xlat
+                && mAlwaysOn == other.mAlwaysOn
+                && mInfrastructureBitmask == other.mInfrastructureBitmask;
     }
 
     /**
@@ -1270,7 +1339,8 @@
                 && Objects.equals(mApnSetId, other.mApnSetId)
                 && Objects.equals(mCarrierId, other.mCarrierId)
                 && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
-                && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+                && Objects.equals(mAlwaysOn, other.mAlwaysOn)
+                && Objects.equals(mInfrastructureBitmask, other.mInfrastructureBitmask);
     }
 
     /**
@@ -1307,7 +1377,8 @@
                 && Objects.equals(this.mApnSetId, other.mApnSetId)
                 && Objects.equals(this.mCarrierId, other.mCarrierId)
                 && Objects.equals(this.mSkip464Xlat, other.mSkip464Xlat)
-                && Objects.equals(this.mAlwaysOn, other.mAlwaysOn);
+                && Objects.equals(this.mAlwaysOn, other.mAlwaysOn)
+                && Objects.equals(this.mInfrastructureBitmask, other.mInfrastructureBitmask);
     }
 
     // Equal or one is null.
@@ -1379,6 +1450,7 @@
         apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
         apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
         apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
+        apnValue.put(Telephony.Carriers.INFRASTRUCTURE_BITMASK, mInfrastructureBitmask);
         return apnValue;
     }
 
@@ -1651,6 +1723,7 @@
         dest.writeInt(mCarrierId);
         dest.writeInt(mSkip464Xlat);
         dest.writeBoolean(mAlwaysOn);
+        dest.writeInt(mInfrastructureBitmask);
     }
 
     private static ApnSetting readFromParcel(Parcel in) {
@@ -1686,6 +1759,7 @@
                 .setCarrierId(in.readInt())
                 .setSkip464Xlat(in.readInt())
                 .setAlwaysOn(in.readBoolean())
+                .setInfrastructureBitmask(in.readInt())
                 .buildWithoutCheck();
     }
 
@@ -1767,6 +1841,7 @@
         private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
         private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
         private boolean mAlwaysOn;
+        private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR;
 
         /**
          * Default constructor for Builder.
@@ -2189,6 +2264,22 @@
         }
 
         /**
+         * Set the infrastructure bitmask.
+         *
+         * @param infrastructureBitmask The infrastructure bitmask of which the APN can be used on.
+         * For example, some APNs can only be used when the device is on cellular, on satellite, or
+         * both.
+         *
+         * @return The builder.
+         * @hide
+         */
+        @NonNull
+        public Builder setInfrastructureBitmask(@InfrastructureBitmask int infrastructureBitmask) {
+            this.mInfrastructureBitmask = infrastructureBitmask;
+            return this;
+        }
+
+        /**
          * Builds {@link ApnSetting} from this builder.
          *
          * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
@@ -2198,7 +2289,7 @@
         public ApnSetting build() {
             if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
                     | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
-                    | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE)) == 0
+                    | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0
                 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
                 return null;
             }
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index f346b92..88a32d1 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -431,6 +431,8 @@
                 return ApnSetting.TYPE_VSIM;
             case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:
                 return ApnSetting.TYPE_ENTERPRISE;
+            case NetworkCapabilities.NET_CAPABILITY_RCS:
+                return ApnSetting.TYPE_RCS;
             default:
                 return ApnSetting.TYPE_NONE;
         }
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index ecd7039..d30078d 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -29,6 +29,7 @@
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.ArrayList;
@@ -545,6 +546,11 @@
             try {
                 iSession.setListener(mIImsCallSessionListenerProxy);
             } catch (RemoteException e) {
+                if (Flags.ignoreAlreadyTerminatedIncomingCallBeforeRegisteringListener()) {
+                    // Registering listener failed, so other operations are not allowed.
+                    Log.e(TAG, "ImsCallSession : " + e);
+                    mClosed = true;
+                }
             }
         } else {
             mClosed = true;
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
index 22412e6..c690f98 100644
--- a/telephony/java/android/telephony/satellite/AntennaDirection.java
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -16,11 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -38,6 +41,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class AntennaDirection implements Parcelable {
     /** Antenna x axis direction. */
     private float mX;
@@ -62,11 +66,13 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mX);
         out.writeFloat(mY);
@@ -74,6 +80,7 @@
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final Creator<AntennaDirection> CREATOR =
             new Creator<>() {
                 @Override
@@ -118,14 +125,17 @@
         return Objects.hash(mX, mY, mZ);
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getX() {
         return mX;
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getY() {
         return mY;
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getZ() {
         return mZ;
     }
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
index 588be6c..8842886 100644
--- a/telephony/java/android/telephony/satellite/AntennaPosition.java
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -16,11 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -29,6 +32,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class AntennaPosition implements Parcelable {
     /** Antenna direction used for satellite communication. */
     @NonNull AntennaDirection mAntennaDirection;
@@ -49,17 +53,20 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(mAntennaDirection, flags);
         out.writeInt(mSuggestedHoldPosition);
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final Creator<AntennaPosition> CREATOR =
             new Creator<>() {
                 @Override
@@ -100,11 +107,13 @@
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public AntennaDirection getAntennaDirection() {
         return mAntennaDirection;
     }
 
     @SatelliteManager.DeviceHoldPosition
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int getSuggestedHoldPosition() {
         return mSuggestedHoldPosition;
     }
diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
new file mode 100644
index 0000000..54cab48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Interface for non-terrestrial signal strength notify callback.
+ * @hide
+ */
+oneway interface INtnSignalStrengthCallback {
+    /**
+     * Called when NTN signal strength changes.
+     * @param ntnSignalStrength The new NTN signal strength.
+     */
+    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
copy to telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
index 6d7c576..a79cb69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
@@ -1,11 +1,11 @@
 /*
- * 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.viewmodel
+package android.telephony.satellite;
 
-enum class QSTileLifecycle {
-    ALIVE,
-    DEAD,
-}
+parcelable NtnSignalStrength;
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
new file mode 100644
index 0000000..16d7654
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * NTN signal strength related information.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public final class NtnSignalStrength implements Parcelable {
+    /** Non-terrestrial network signal strength is not available. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_NONE = 0;
+    /** Non-terrestrial network signal strength is poor. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_POOR = 1;
+    /** Non-terrestrial network signal strength is moderate. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2;
+    /** Non-terrestrial network signal strength is good. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_GOOD = 3;
+    /** Non-terrestrial network signal strength is great. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_GREAT = 4;
+    @NtnSignalStrengthLevel private int mLevel;
+
+    /** @hide */
+    @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = {
+            NTN_SIGNAL_STRENGTH_NONE,
+            NTN_SIGNAL_STRENGTH_POOR,
+            NTN_SIGNAL_STRENGTH_MODERATE,
+            NTN_SIGNAL_STRENGTH_GOOD,
+            NTN_SIGNAL_STRENGTH_GREAT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NtnSignalStrengthLevel {}
+
+    /**
+     * Create a parcelable object to inform the current non-terrestrial signal strength
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public NtnSignalStrength(@NtnSignalStrengthLevel int level) {
+        this.mLevel = level;
+    }
+
+    /**
+     * This constructor is used to create a copy of an existing NtnSignalStrength object.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public NtnSignalStrength(@Nullable NtnSignalStrength source) {
+        this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel();
+    }
+
+    private NtnSignalStrength(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NtnSignalStrengthLevel public int getLevel() {
+        return mLevel;
+    }
+
+    /**
+     * @return 0
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @param out  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}.
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mLevel);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mLevel = in.readInt();
+    }
+
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NonNull public static final Creator<NtnSignalStrength> CREATOR =
+            new Creator<NtnSignalStrength>() {
+                @Override public NtnSignalStrength createFromParcel(Parcel in) {
+                    return new NtnSignalStrength(in);
+                }
+
+                @Override public NtnSignalStrength[] newArray(int size) {
+                    return new NtnSignalStrength[size];
+                }
+            };
+
+    @Override
+    public int hashCode() {
+        return mLevel;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+
+        NtnSignalStrength that = (NtnSignalStrength) obj;
+        return mLevel == that.mLevel;
+    }
+
+    @Override public String toString() {
+        return "NtnSignalStrength{"
+                + "mLevel=" + mLevel
+                + '}';
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
new file mode 100644
index 0000000..4b79590
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for notifying satellite signal strength change.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface NtnSignalStrengthCallback {
+    /**
+     * Called when non-terrestrial network signal strength changes.
+     * @param ntnSignalStrength The new non-terrestrial network signal strength.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index dc4d38b..022a856 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -16,11 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -30,6 +33,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class PointingInfo implements Parcelable {
     /** Satellite azimuth in degrees */
     private float mSatelliteAzimuthDegrees;
@@ -50,16 +54,19 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mSatelliteAzimuthDegrees);
         out.writeFloat(mSatelliteElevationDegrees);
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
             new Creator<PointingInfo>() {
                 @Override
@@ -101,10 +108,12 @@
         return sb.toString();
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getSatelliteAzimuthDegrees() {
         return mSatelliteAzimuthDegrees;
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getSatelliteElevationDegrees() {
         return mSatelliteElevationDegrees;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index bc45be1..0d8f101 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -16,12 +16,15 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -34,6 +37,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class SatelliteCapabilities implements Parcelable {
     /**
      * List of technologies supported by the satellite modem.
@@ -76,11 +80,13 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
             out.writeInt(mSupportedRadioTechnologies.size());
@@ -106,6 +112,7 @@
         }
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
         @Override
         public SatelliteCapabilities createFromParcel(Parcel in) {
@@ -165,6 +172,7 @@
     /**
      * @return The list of technologies supported by the satellite modem.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull @SatelliteManager.NTRadioTechnology public Set<Integer>
             getSupportedRadioTechnologies() {
         return mSupportedRadioTechnologies;
@@ -176,6 +184,7 @@
      * @return {@code true} if UE needs to point to a satellite to send and receive data and
      *         {@code false} otherwise.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public boolean isPointingRequired() {
         return mIsPointingRequired;
     }
@@ -185,6 +194,7 @@
      *
      * @return The maximum number of bytes per datagram that can be sent over satellite.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int getMaxBytesPerOutgoingDatagram() {
         return mMaxBytesPerOutgoingDatagram;
     }
@@ -195,6 +205,7 @@
      * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
      */
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public Map<Integer, AntennaPosition> getAntennaPositionMap() {
         return mAntennaPositionMap;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index 9037f0c..4d67f80 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -16,17 +16,21 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * SatelliteDatagram is used to store data that is to be sent or received over satellite.
  * Data is stored in byte array format.
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class SatelliteDatagram implements Parcelable {
     /**
      * Datagram to be sent or received over satellite.
@@ -45,15 +49,18 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeByteArray(mData);
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull public static final Creator<SatelliteDatagram> CREATOR =
             new Creator<>() {
                 @Override
@@ -73,6 +80,7 @@
      * satellite provider. Client application should be aware of how to encode the datagram based
      * upon the satellite provider.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull public byte[] getSatelliteDatagram() {
         return mData;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index cb2920f..b5763c3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -16,9 +16,12 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.function.Consumer;
 
 /**
@@ -27,6 +30,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteDatagramCallback {
     /**
      * Called when there is an incoming datagram to be received.
@@ -38,6 +42,7 @@
      *                 that they received the datagram. If the callback is not received within
      *                 five minutes, Telephony will resend the datagram.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
             int pendingCount, @NonNull Consumer<Void> callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 8dc2de8..21d93bd 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -35,7 +35,9 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
@@ -63,6 +65,7 @@
  */
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
@@ -76,6 +79,8 @@
     private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
             ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
             new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback>
+            sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();
 
     private final int mSubId;
 
@@ -108,6 +113,7 @@
     /**
      * Exception from the satellite service containing the {@link SatelliteResult} error code.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static class SatelliteException extends Exception {
         @SatelliteResult private final int mErrorCode;
 
@@ -116,6 +122,7 @@
          *
          * @param errorCode The {@link SatelliteResult}.
          */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         public SatelliteException(@SatelliteResult int errorCode) {
             mErrorCode = errorCode;
         }
@@ -125,6 +132,7 @@
          *
          * @return The {@link SatelliteResult}.
          */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         @SatelliteResult public int getErrorCode() {
             return mErrorCode;
         }
@@ -188,106 +196,137 @@
     public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
 
     /**
+     * Bundle key to get the response from
+     * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+
+    public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
+
+    /**
      * The request was successfully processed.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SUCCESS = 0;
     /**
      * A generic error which should be used only when other specific errors cannot be used.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_ERROR = 1;
     /**
      * Error received from the satellite server.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVER_ERROR = 2;
     /**
      * Error received from the vendor service. This generic error code should be used
      * only when the error cannot be mapped to other specific service error codes.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVICE_ERROR = 3;
     /**
      * Error received from satellite modem. This generic error code should be used only when
      * the error cannot be mapped to other specific modem error codes.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_MODEM_ERROR = 4;
     /**
      * Error received from the satellite network. This generic error code should be used only when
      * the error cannot be mapped to other specific network error codes.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NETWORK_ERROR = 5;
     /**
      * Telephony is not in a valid state to receive requests from clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6;
     /**
      * Satellite modem is not in a valid state to receive requests from clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7;
     /**
      * Either vendor service, or modem, or Telephony framework has received a request with
      * invalid arguments from its clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8;
     /**
      * Telephony framework failed to send a request or receive a response from the vendor service
      * or satellite modem due to internal error.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_FAILED = 9;
     /**
      * Radio did not start or is resetting.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10;
     /**
      * The request is not supported by either the satellite modem or the network.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11;
     /**
      * Satellite modem or network has no resources available to handle requests from clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NO_RESOURCES = 12;
     /**
      * Satellite service is not provisioned yet.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13;
     /**
      * Satellite service provision is already in progress.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14;
     /**
      * The ongoing request was aborted by either the satellite modem or the network.
      * This error is also returned when framework decides to abort current send request as one
      * of the previous send request failed.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15;
     /**
      * The device/subscriber is barred from accessing the satellite service.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_ACCESS_BARRED = 16;
     /**
      * Satellite modem timeout to receive ACK or response from the satellite network after
      * sending a request to the network.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17;
     /**
      * Satellite network is not reachable from the modem.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NOT_REACHABLE = 18;
     /**
      * The device/subscriber is not authorized to register with the satellite service provider.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19;
     /**
      * The device does not support satellite.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20;
 
     /**
      * The current request is already in-progress.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21;
 
     /**
      * Satellite modem is currently busy due to which current request cannot be processed.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
 
     /** @hide */
@@ -323,22 +362,27 @@
      * Unknown Non-Terrestrial radio technology. This generic radio technology should be used
      * only when the radio technology cannot be mapped to other specific radio technologies.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0;
     /**
      * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1;
     /**
      * 3GPP 5G NR over Non-Terrestrial-Networks technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2;
     /**
      * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3;
     /**
      * Proprietary technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4;
 
     /** @hide */
@@ -353,12 +397,16 @@
     public @interface NTRadioTechnology {}
 
     /** Suggested device hold position is unknown. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
     /** User is suggested to hold the device in portrait mode. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
     /** User is suggested to hold the device in landscape mode with left hand. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
     /** User is suggested to hold the device in landscape mode with right hand. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
 
     /** @hide */
@@ -372,14 +420,18 @@
     public @interface DeviceHoldPosition {}
 
     /** Display mode is unknown. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_UNKNOWN = 0;
     /** Display mode of the device used for satellite communication for non-foldable phones. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_FIXED = 1;
     /** Display mode of the device used for satellite communication for foldabale phones when the
      * device is opened. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_OPENED = 2;
     /** Display mode of the device used for satellite communication for foldabable phones when the
      * device is closed. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_CLOSED = 3;
 
     /** @hide */
@@ -414,7 +466,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -457,7 +509,7 @@
      * @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,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -512,7 +564,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -565,7 +617,7 @@
      *
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -619,7 +671,7 @@
      * @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,
             @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -664,16 +716,19 @@
      * The default state indicating that datagram transfer is idle.
      * This should be sent if there are no message transfer activity happening.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0;
     /**
      * A transition state indicating that a datagram is being sent.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1;
     /**
      * An end state indicating that datagram sending completed successfully.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2;
     /**
      * An end state indicating that datagram sending completed with a failure.
@@ -681,16 +736,19 @@
      * must be sent before reporting any additional datagram transfer state changes. All pending
      * messages will be reported as failed, to the corresponding applications.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3;
     /**
      * A transition state indicating that a datagram is being received.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4;
     /**
      * An end state indicating that datagram receiving completed successfully.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5;
     /**
      * An end state indicating that datagram receive operation found that there are no
@@ -698,12 +756,14 @@
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6;
     /**
      * An end state indicating that datagram receive completed with a failure.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7;
     /**
      * A transition state indicating that Telephony is waiting for satellite modem to connect to a
@@ -721,6 +781,7 @@
      * only when the datagram transfer state cannot be mapped to other specific datagram transfer
      * states.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1;
 
     /** @hide */
@@ -743,26 +804,32 @@
     /**
      * Satellite modem is in idle state.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_IDLE = 0;
     /**
      * Satellite modem is listening for incoming datagrams.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_LISTENING = 1;
     /**
      * Satellite modem is sending and/or receiving datagrams.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2;
     /**
      * Satellite modem is retrying to send and/or receive datagrams.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3;
     /**
      * Satellite modem is powered off.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_OFF = 4;
     /**
      * Satellite modem is unavailable.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5;
     /**
      * The satellite modem is powered on but the device is not registered to a satellite cell.
@@ -778,6 +845,7 @@
      * Satellite modem state is unknown. This generic modem state should be used only when the
      * modem state cannot be mapped to other specific modem states.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1;
 
     /** @hide */
@@ -799,15 +867,18 @@
      * Datagram type is unknown. This generic datagram type should be used only when the
      * datagram type cannot be mapped to other specific datagram types.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_UNKNOWN = 0;
     /**
      * Datagram type indicating that the datagram to be sent or received is of type SOS message.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1;
     /**
      * Datagram type indicating that the datagram to be sent or received is of type
      * location sharing.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
 
     /** @hide */
@@ -857,7 +928,7 @@
      * @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,
             @SatelliteResult @NonNull Consumer<Integer> resultListener,
             @NonNull SatelliteTransmissionUpdateCallback callback) {
@@ -927,7 +998,7 @@
      * @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,
@@ -983,7 +1054,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
@@ -1036,7 +1107,7 @@
      * @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,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -1076,7 +1147,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @SatelliteResult public int registerForSatelliteProvisionStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteProvisionStateCallback callback) {
@@ -1119,7 +1190,7 @@
      * @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(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
@@ -1158,7 +1229,7 @@
      * @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,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1210,7 +1281,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @SatelliteResult public int registerForSatelliteModemStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteStateCallback callback) {
@@ -1250,7 +1321,7 @@
      * @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) {
         Objects.requireNonNull(callback);
         ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
@@ -1288,7 +1359,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @SatelliteResult public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
@@ -1344,7 +1415,7 @@
      * @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) {
         Objects.requireNonNull(callback);
         ISatelliteDatagramCallback internalCallback =
@@ -1383,7 +1454,7 @@
      * @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,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
@@ -1436,7 +1507,7 @@
      * @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,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull @CallbackExecutor Executor executor,
@@ -1482,7 +1553,7 @@
      * @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(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -1540,7 +1611,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1594,7 +1665,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void setDeviceAlignedWithSatellite(boolean isAligned) {
         try {
             ITelephony telephony = getITelephony();
@@ -1628,7 +1699,7 @@
      * @param subId The subscription ID of the carrier.
      * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
@@ -1662,7 +1733,7 @@
      *                 will return a {@code boolean} with value {@code true} if the satellite
      *                 is enabled and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 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.
@@ -1688,7 +1759,7 @@
      * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
@@ -1731,7 +1802,7 @@
      * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
@@ -1807,6 +1878,165 @@
         return new HashSet<>();
     }
 
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+     * </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)} will return an instance of
+     * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel}
+     * 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}.
+     *
+     * @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)
+    @NonNull
+    public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) {
+                                NtnSignalStrength ntnSignalStrength =
+                                        resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH,
+                                                NtnSignalStrength.class);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(ntnSignalStrength)));
+                            } else {
+                                loge("KEY_NTN_SIGNAL_STRENGTH does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestNtnSignalStrength(mSubId, receiver);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("requestNtnSignalStrength() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+     * </p>
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     *
+     * @return 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)
+    @SatelliteResult public int registerForNtnSignalStrengthChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull NtnSignalStrengthCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                INtnSignalStrengthCallback internalCallback =
+                        new INtnSignalStrengthCallback.Stub() {
+                            @Override
+                            public void onNtnSignalStrengthChanged(
+                                    NtnSignalStrength ntnSignalStrength) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onNtnSignalStrengthChanged(
+                                                ntnSignalStrength)));
+                            }
+                        };
+                sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
+                return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_RESULT_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}..
+     * </p>
+     *
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+     *
+     * @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 unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) {
+        Objects.requireNonNull(callback);
+        INtnSignalStrengthCallback internalCallback =
+                sNtnSignalStrengthCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+
+    }
+
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 7116876..a12952b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,14 +16,18 @@
 
 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 provision state change events.
  *
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteProvisionStateCallback {
     /**
      * Called when satellite provision state changes.
@@ -33,5 +37,6 @@
      *                    It is generally expected that the provisioning app retries if
      *                    provisioning fails.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteProvisionStateChanged(boolean provisioned);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 812ff2d..bfe6e10 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,18 +16,23 @@
 
 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/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 7ac06b0..e020970 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -16,9 +16,12 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * A callback class for monitoring satellite position update and datagram transfer state change
  * events.
@@ -26,12 +29,14 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteTransmissionUpdateCallback {
     /**
      * Called when the satellite position changed.
      *
      * @param pointingInfo The pointing info containing the satellite location.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
 
     /**
@@ -41,6 +46,7 @@
      * @param sendPendingCount The number of datagrams that are currently being sent.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSendDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
             @SatelliteManager.SatelliteResult int errorCode);
@@ -52,6 +58,7 @@
      * @param receivePendingCount The number of datagrams that are currently pending to be received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onReceiveDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
             @SatelliteManager.SatelliteResult int errorCode);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
index 0803a01..b7712bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -12,15 +12,17 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared
+package android.telephony.satellite.stub;
 
-import android.appwidget.AppWidgetProviderInfo
+import android.telephony.satellite.NtnSignalStrength;
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+/**
+ * Consumer pattern for a request that receives the signal strength of non-terrestrial network from
+ * the SatelliteService.
+ * @hide
+ */
+oneway interface INtnSignalStrengthConsumer {
+    void accept(in NtnSignalStrength result);
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 0fcd0d6..e2cd4f8 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
 import android.telephony.satellite.stub.ISatelliteListener;
 import android.telephony.satellite.stub.SatelliteDatagram;
@@ -32,15 +33,15 @@
      *
      * @param listener The callback interface to handle satellite service indications.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void setSatelliteListener(in ISatelliteListener listener);
 
@@ -53,15 +54,15 @@
      *                disabling listening mode.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteListeningEnabled(in boolean enable, in int timeout,
             in IIntegerConsumer resultCallback);
@@ -84,15 +85,15 @@
      * @param enableDemoMode True to enable demo mode and false to disable.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
             in IIntegerConsumer resultCallback);
@@ -101,39 +102,42 @@
      * Request to get whether the satellite modem is enabled.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite modem is enabled.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite modem is enabled.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
+    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback,
+            in IBooleanConsumer callback);
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite service is supported on the device.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite service is supported on the device.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestIsSatelliteSupported(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
@@ -142,19 +146,20 @@
      * Request to get the SatelliteCapabilities of the satellite service.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the SatelliteCapabilities of the satellite service.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the SatelliteCapabilities of the satellite service.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteCapabilities(in IIntegerConsumer resultCallback,
             in ISatelliteCapabilitiesConsumer callback);
@@ -166,15 +171,15 @@
      *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
@@ -184,15 +189,15 @@
      *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
@@ -206,18 +211,18 @@
      * @param provisionData Data from the provisioning app that can be used by provisioning server
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     void provisionSatelliteService(in String token, in byte[] provisionData,
             in IIntegerConsumer resultCallback);
@@ -230,18 +235,18 @@
      * @param token The token of the device/subscription to be deprovisioned.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
 
@@ -249,19 +254,20 @@
      * Request to get whether this device is provisioned with a satellite provider.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether this device is provisioned with a satellite provider.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether this device is provisioned with a satellite provider.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
@@ -273,20 +279,20 @@
      *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
     void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
 
@@ -297,21 +303,21 @@
      * @param isEmergency Whether this is an emergency datagram.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
     void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
             in IIntegerConsumer resultCallback);
@@ -322,19 +328,20 @@
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the current satellite modem state.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the current satellite modem state.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteModemState(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
@@ -343,19 +350,20 @@
      * Request to get whether satellite communication is allowed for the current location.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether satellite communication is allowed for the current location.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether satellite communication is allowed for the current location.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestIsSatelliteCommunicationAllowedForCurrentLocation(
             in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
@@ -366,19 +374,20 @@
      * This will return 0 if the satellite is currently visible.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the time after which the satellite will be visible.
+     *                       This must only be sent when the result is not
+          *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the time after which the satellite will be visible.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
@@ -399,14 +408,14 @@
      *                             attach to them.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:NONE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:MODEM_ERR
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList,
             in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback);
@@ -420,12 +429,12 @@
      * @param serial Serial number of request.
      * @param enable {@code true} to enable satellite, {@code false} to disable satellite.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled,
          in IIntegerConsumer callback);
@@ -437,13 +446,59 @@
      *                this information to determine the relevant carrier.
      * @param serial Serial number of request.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     */
+    void requestSignalStrength(in IIntegerConsumer resultCallback,
+            in INtnSignalStrengthConsumer callback);
+
+    /**
+     * The satellite service should report the NTN signal strength via
+     * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
+     *
+     * Note: This API can be invoked multiple times. If the modem is already in the expected
+     * state from a previous request, subsequent invocations may be ignored.
+     *
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     */
+     void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
+
+    /**
+     * The satellite service should stop reporting NTN signal strength to the framework. This will
+     * be called when device is screen off to save power by not letting signal strength updates to
+     * wake up application processor.
+     *
+     * Note: This API can be invoked multiple times. If the modem is already in the expected
+     * state from a previous request, subsequent invocations may be ignored.
+     *
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     */
+     void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d687162..d44ddfa 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.NtnSignalStrength;
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.PointingInfo;
 import android.telephony.satellite.stub.SatelliteDatagram;
@@ -58,4 +59,10 @@
      * @param state The current satellite modem state.
      */
     void onSatelliteModemStateChanged(in SatelliteModemState state);
+
+    /**
+     * Called when NTN signal strength changes.
+     * @param ntnSignalStrength The new NTN signal strength.
+     */
+    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
new file mode 100644
index 0000000..f489005
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.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.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.NtnSignalStrengthLevel;
+
+/**
+ * @hide
+ */
+parcelable NtnSignalStrength {
+    /**
+     * Non-terrestrial signal strength. The value represents the level of signal strength which can
+     * be translated to the number of signal bars.
+     */
+    NtnSignalStrengthLevel signalStrengthLevel;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
index 0803a01..53b1373 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
@@ -12,15 +12,18 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared
+package android.telephony.satellite.stub;
 
-import android.appwidget.AppWidgetProviderInfo
-
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum NtnSignalStrengthLevel {
+    NTN_SIGNAL_STRENGTH_NONE = 0,
+    NTN_SIGNAL_STRENGTH_POOR = 1,
+    NTN_SIGNAL_STRENGTH_MODERATE = 2,
+    NTN_SIGNAL_STRENGTH_GOOD = 3,
+    NTN_SIGNAL_STRENGTH_GREAT = 4
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index a9c09c9..a636a61 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -72,175 +72,199 @@
 
         @Override
         public void requestSatelliteListeningEnabled(boolean enable, int timeout,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteListeningEnabled(enable, timeout, errorCallback),
+                            .requestSatelliteListeningEnabled(enable, timeout, resultCallback),
                     "requestSatelliteListeningEnabled");
         }
 
         @Override
         public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .enableCellularModemWhileSatelliteModeIsOn(enabled, errorCallback),
+                            .enableCellularModemWhileSatelliteModeIsOn(enabled, resultCallback),
                     "enableCellularModemWhileSatelliteModeIsOn");
         }
 
         @Override
         public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
                             .requestSatelliteEnabled(
-                                    enableSatellite, enableDemoMode, errorCallback),
+                                    enableSatellite, enableDemoMode, resultCallback),
                     "requestSatelliteEnabled");
         }
 
         @Override
-        public void requestIsSatelliteEnabled(IIntegerConsumer errorCallback,
+        public void requestIsSatelliteEnabled(IIntegerConsumer resultCallback,
                 IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteEnabled(errorCallback, callback),
+                            .requestIsSatelliteEnabled(resultCallback, callback),
                     "requestIsSatelliteEnabled");
         }
 
         @Override
-        public void requestIsSatelliteSupported(IIntegerConsumer errorCallback,
+        public void requestIsSatelliteSupported(IIntegerConsumer resultCallback,
                 IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteSupported(errorCallback, callback),
+                            .requestIsSatelliteSupported(resultCallback, callback),
                     "requestIsSatelliteSupported");
         }
 
         @Override
-        public void requestSatelliteCapabilities(IIntegerConsumer errorCallback,
+        public void requestSatelliteCapabilities(IIntegerConsumer resultCallback,
                 ISatelliteCapabilitiesConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteCapabilities(errorCallback, callback),
+                            .requestSatelliteCapabilities(resultCallback, callback),
                     "requestSatelliteCapabilities");
         }
 
         @Override
-        public void startSendingSatellitePointingInfo(IIntegerConsumer errorCallback)
+        public void startSendingSatellitePointingInfo(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(errorCallback),
+                    () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(resultCallback),
                     "startSendingSatellitePointingInfo");
         }
 
         @Override
-        public void stopSendingSatellitePointingInfo(IIntegerConsumer errorCallback)
+        public void stopSendingSatellitePointingInfo(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(errorCallback),
+                    () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(resultCallback),
                     "stopSendingSatellitePointingInfo");
         }
 
         @Override
         public void provisionSatelliteService(String token, byte[] provisionData,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .provisionSatelliteService(token, provisionData, errorCallback),
+                            .provisionSatelliteService(token, provisionData, resultCallback),
                     "provisionSatelliteService");
         }
 
         @Override
-        public void deprovisionSatelliteService(String token, IIntegerConsumer errorCallback)
+        public void deprovisionSatelliteService(String token, IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.deprovisionSatelliteService(token, errorCallback),
+                    () -> SatelliteImplBase.this.deprovisionSatelliteService(token, resultCallback),
                     "deprovisionSatelliteService");
         }
 
         @Override
-        public void requestIsSatelliteProvisioned(IIntegerConsumer errorCallback,
+        public void requestIsSatelliteProvisioned(IIntegerConsumer resultCallback,
                 IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteProvisioned(errorCallback, callback),
+                            .requestIsSatelliteProvisioned(resultCallback, callback),
                     "requestIsSatelliteProvisioned");
         }
 
         @Override
-        public void pollPendingSatelliteDatagrams(IIntegerConsumer errorCallback)
+        public void pollPendingSatelliteDatagrams(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(errorCallback),
+                    () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(resultCallback),
                     "pollPendingSatelliteDatagrams");
         }
 
         @Override
         public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
+                            .sendSatelliteDatagram(datagram, isEmergency, resultCallback),
                     "sendSatelliteDatagram");
         }
 
         @Override
-        public void requestSatelliteModemState(IIntegerConsumer errorCallback,
+        public void requestSatelliteModemState(IIntegerConsumer resultCallback,
                 IIntegerConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteModemState(errorCallback, callback),
+                            .requestSatelliteModemState(resultCallback, callback),
                     "requestSatelliteModemState");
         }
 
         @Override
         public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                IIntegerConsumer errorCallback, IBooleanConsumer callback)
+                IIntegerConsumer resultCallback, IBooleanConsumer callback)
                 throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
                             .requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                                    errorCallback, callback),
+                                    resultCallback, callback),
                     "requestIsSatelliteCommunicationAllowedForCurrentLocation");
         }
 
         @Override
-        public void requestTimeForNextSatelliteVisibility(IIntegerConsumer errorCallback,
+        public void requestTimeForNextSatelliteVisibility(IIntegerConsumer resultCallback,
                 IIntegerConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestTimeForNextSatelliteVisibility(errorCallback, callback),
+                            .requestTimeForNextSatelliteVisibility(resultCallback, callback),
                     "requestTimeForNextSatelliteVisibility");
         }
 
         @Override
         public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList,
-                List<String> devicePlmnList, IIntegerConsumer errorCallback)
+                List<String> devicePlmnList, IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this.setSatellitePlmn(
-                            simSlot, carrierPlmnList, devicePlmnList, errorCallback),
+                            simSlot, carrierPlmnList, devicePlmnList, resultCallback),
                     "setSatellitePlmn");
         }
 
         @Override
         public void setSatelliteEnabledForCarrier(int simSlot, boolean enableSatellite,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this
-                            .setSatelliteEnabledForCarrier(simSlot, enableSatellite, errorCallback),
+                    () -> SatelliteImplBase.this.setSatelliteEnabledForCarrier(
+                            simSlot, enableSatellite, resultCallback),
                     "setSatelliteEnabledForCarrier");
         }
 
         @Override
-        public void requestIsSatelliteEnabledForCarrier(int simSlot, IIntegerConsumer errorCallback,
-                IBooleanConsumer callback) throws RemoteException {
+        public void requestIsSatelliteEnabledForCarrier(int simSlot,
+                IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteEnabledForCarrier(simSlot, errorCallback, callback),
+                            .requestIsSatelliteEnabledForCarrier(simSlot, resultCallback, callback),
                     "requestIsSatelliteEnabledForCarrier");
         }
 
+        @Override
+        public void requestSignalStrength(IIntegerConsumer resultCallback,
+                INtnSignalStrengthConsumer callback) throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback),
+                    "requestSignalStrength");
+        }
+
+        @Override
+        public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback),
+                    "startSendingNtnSignalStrength");
+        }
+
+        @Override
+        public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback),
+                    "stopSendingNtnSignalStrength");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -260,15 +284,15 @@
      *
      * @param listener The callback interface to handle satellite service indications.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void setSatelliteListener(@NonNull ISatelliteListener listener) {
         // stub implementation
@@ -281,20 +305,20 @@
      * @param enable True to enable satellite listening mode and false to disable.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void requestSatelliteListeningEnabled(boolean enable, int timeout,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -302,10 +326,10 @@
      * Allow cellular modem scanning while satellite mode is on.
      * @param enabled  {@code true} to enable cellular modem while satellite mode is on
      * and {@code false} to disable
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      */
     public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -316,42 +340,43 @@
      *
      * @param enableSatellite True to enable the satellite modem and false to disable.
      * @param enableDemoMode True to enable demo mode and false to disable.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite modem is enabled.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite modem is enabled.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback,
+    public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer resultCallback,
             @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
@@ -359,22 +384,23 @@
     /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite service is supported on the device.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite service is supported on the device.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback,
+    public void requestIsSatelliteSupported(@NonNull IIntegerConsumer resultCallback,
             @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
@@ -382,22 +408,23 @@
     /**
      * Request to get the SatelliteCapabilities of the satellite service.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the SatelliteCapabilities of the satellite service.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the SatelliteCapabilities of the satellite service.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback,
+    public void requestSatelliteCapabilities(@NonNull IIntegerConsumer resultCallback,
             @NonNull ISatelliteCapabilitiesConsumer callback) {
         // stub implementation
     }
@@ -407,19 +434,19 @@
      * The satellite service should report the satellite pointing info via
      * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+    public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -427,19 +454,19 @@
      * User stopped pointing to the satellite.
      * The satellite service should stop reporting satellite pointing info to the framework.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+    public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -452,23 +479,23 @@
      *              gateway.
      * @param provisionData Data from the provisioning app that can be used by provisioning
      *                      server
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -478,45 +505,46 @@
      * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     public void deprovisionSatelliteService(@NonNull String token,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
     /**
      * Request to get whether this device is provisioned with a satellite provider.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether this device is provisioned with a satellite provider.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether this device is provisioned with a satellite provider.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback,
+    public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer resultCallback,
             @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
@@ -526,24 +554,24 @@
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
-    public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) {
+    public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -552,26 +580,26 @@
      *
      * @param datagram Datagram to send in byte format.
      * @param isEmergency Whether this is an emergency datagram.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
     public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -580,22 +608,23 @@
      * The satellite service should report the current satellite modem state via
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the current satellite modem state.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the current satellite modem state.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback,
+    public void requestSatelliteModemState(@NonNull IIntegerConsumer resultCallback,
             @NonNull IIntegerConsumer callback) {
         // stub implementation
     }
@@ -603,23 +632,24 @@
     /**
      * Request to get whether satellite communication is allowed for the current location.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether satellite communication is allowed for the current location.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether satellite communication is allowed for the current location.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-            @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+            @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
 
@@ -628,22 +658,23 @@
      * representing the duration in seconds after which the satellite will be visible.
      * This will return 0 if the satellite is currently visible.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the time after which the satellite will be visible.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the time after which the satellite will be visible.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback,
+    public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer resultCallback,
             @NonNull IIntegerConsumer callback) {
         // stub implementation
     }
@@ -658,25 +689,25 @@
      *
      * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
      * applied. The modem will use this information to determine the relevant carrier.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
      *                        supported by user subscription.
      * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
      *                             PLMNs that are not supported by the carrier and make sure not to
      *                             attach to them.
      *
-     * Valid error codes returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:NONE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:MODEM_ERR
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
             @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -689,12 +720,12 @@
      * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite.
      * @param callback {@code true} to enable satellite, {@code false} to disable satellite.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
             @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) {
@@ -707,18 +738,49 @@
      *
      * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
      * applied. The modem will use this information to determine the relevant carrier.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      * @param callback {@code true} to satellite enabled, {@code false} to satellite disabled.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
-            @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+            @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
+        // stub implementation
+    }
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     */
+    public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback,
+            INtnSignalStrengthConsumer callback) {
+        // stub implementation
+    }
+
+    /**
+     * Requests to deliver signal strength changed events through the
+     * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)}
+     * callback.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) {
+        // stub implementation
+    }
+
+    /**
+     * Requests to stop signal strength changed events
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){
         // stub implementation
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3aa5a5a..58e7026 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,10 +67,12 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import com.android.ims.internal.IImsServiceFeatureCallback;
@@ -2837,7 +2839,6 @@
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
 
-
     /**
      * Registers for provision state changed from satellite modem.
      *
@@ -3071,4 +3072,40 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param receiver Result receiver to get the error code of the request and the current signal
+     * strength of the satellite connection.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     *
+     * @return The {@link SatelliteResult} result of the operation.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for provision state changed.
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForNtnSignalStrengthChanged(int subId,
+            in INtnSignalStrengthCallback callback);
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 72e4389..a20f26c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -120,31 +120,6 @@
     int BLOCKED_DUE_TO_CALL = 69;                   /* SMS is blocked due to call control */
     int RF_HARDWARE_ISSUE = 70;                     /* RF HW issue is detected */
     int NO_RF_CALIBRATION_INFO = 71;                /* No RF calibration in device */
-    int ENCODING_NOT_SUPPORTED = 72;                /* The encoding scheme is not supported by
-                                                       either the network or the MS. */
-    int FEATURE_NOT_SUPPORTED = 73;                 /* The requesting feature is not supported by
-                                                       the service provider. */
-    int INVALID_CONTACT = 74;                       /* The contact to be added is either not
-                                                       existing or not valid. */
-    int MODEM_INCOMPATIBLE = 75;                    /* The modem of the MS is not compatible with
-                                                       the service provider. */
-    int NETWORK_TIMEOUT = 76;                       /* Modem timeout to receive ACK or response from
-                                                       network after sending a request to it. */
-    int NO_SATELLITE_SIGNAL = 77;                   /* Modem fails to communicate with the satellite
-                                                       network since there is no satellite signal.*/
-    int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78;        /* The request cannot be performed since the
-                                                       subscriber's account balance is not
-                                                       sufficient. */
-    int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79;        /* The radio technology is not supported by the
-                                                       service provider. */
-    int SUBSCRIBER_NOT_AUTHORIZED = 80;             /* The subscription is not authorized to
-                                                       register with the service provider. */
-    int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the
-                                                       Framework the satellite modem detects
-                                                       terrestrial signal, aborts the request, and
-                                                       switches to the terrestrial network. */
-    int UNIDENTIFIED_SUBSCRIBER = 82;               /* The subscriber is not registered with the
-                                                       service provider */
 
     // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
     // reveal particular replacement for Generic failure
@@ -568,22 +543,7 @@
     int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
     int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
     int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
-    int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
-    int RIL_REQUEST_SET_SATELLITE_POWER = 246;
-    int RIL_REQUEST_GET_SATELLITE_POWER = 247;
-    int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
-    int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
-    int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
-    int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
-    int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
-    int RIL_REQUEST_GET_SATELLITE_MODE = 253;
-    int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
-    int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
-    int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
-    int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
-    int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
-    int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259;
-    int RIL_REQUEST_SET_SATELLITE_PLMN = 260;
+    int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -645,13 +605,6 @@
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
     int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
-    int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
-    int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
-    int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
-    int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
-    int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
-    int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
-    int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
 
     /* The following unsols are not defined in RIL.h */
     int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index d1a68d4..f61cce6 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -200,7 +200,7 @@
     method @Deprecated public int checkPermission(String, String);
     method @Deprecated public int checkSignatures(String, String);
     method @Deprecated public int checkSignatures(int, int);
-    method public void clearInstantAppCookie();
+    method @Deprecated public void clearInstantAppCookie();
     method @Deprecated public void clearPackagePreferredActivities(String);
     method @Deprecated public String[] currentToCanonicalPackageNames(String[]);
     method @Deprecated public void extendVerificationTimeout(int, int, long);
@@ -222,15 +222,15 @@
     method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
     method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ChangedPackages getChangedPackages(int);
+    method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);
     method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);
     method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();
     method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);
     method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
     method @Deprecated public String getInstallerPackageName(String);
-    method public byte[] getInstantAppCookie();
-    method public int getInstantAppCookieMaxBytes();
+    method @Deprecated public byte[] getInstantAppCookie();
+    method @Deprecated public int getInstantAppCookieMaxBytes();
     method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
     method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
@@ -239,7 +239,7 @@
     method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInstaller getPackageInstaller();
+    method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();
     method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public String[] getPackagesForUid(int);
     method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int);
@@ -263,8 +263,8 @@
     method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);
     method @Deprecated public boolean hasSystemFeature(String);
     method @Deprecated public boolean hasSystemFeature(String, int);
-    method public boolean isInstantApp();
-    method public boolean isInstantApp(String);
+    method @Deprecated public boolean isInstantApp();
+    method @Deprecated public boolean isInstantApp(String);
     method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);
     method @Deprecated public boolean isSafeMode();
     method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -281,11 +281,12 @@
     method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);
     method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int);
-    method public void setApplicationCategoryHint(String, int);
+    method @Deprecated public void setApplicationCategoryHint(String, int);
     method @Deprecated public void setApplicationEnabledSetting(String, int, int);
     method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method @Deprecated public void setInstallerPackageName(String, String);
-    method public void updateInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);
     method @Deprecated public void verifyPendingInstall(int, int);
   }
 
diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt
index 1496c35..0c800b6 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -9,7 +9,7 @@
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
     method @Deprecated public String getDefaultBrowserPackageName(int);
     method @Deprecated public boolean setDefaultBrowserPackageName(String, int);
-    method public boolean setInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
   }
 
 }
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index 9e022f0..f350957 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -9,30 +9,30 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public boolean arePermissionsIndividuallyControlled();
+    method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method @Deprecated public boolean arePermissionsIndividuallyControlled();
     method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String);
-    method public String getDefaultBrowserPackageNameAsUser(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
-    method public android.graphics.drawable.Drawable getInstantAppIcon(String);
-    method public android.content.ComponentName getInstantAppInstallerComponent();
-    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
-    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
-    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
-    method public int getIntentVerificationStatusAsUser(String, int);
-    method public int getPermissionFlags(String, String, android.os.UserHandle);
-    method public void grantRuntimePermission(String, String, android.os.UserHandle);
-    method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
-    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public void revokeRuntimePermission(String, String, android.os.UserHandle);
-    method public boolean setDefaultBrowserPackageNameAsUser(String, int);
+    method @Deprecated public String getDefaultBrowserPackageNameAsUser(int);
+    method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String);
+    method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent();
+    method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent();
+    method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+    method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
+    method @Deprecated public int getIntentVerificationStatusAsUser(String, int);
+    method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle);
+    method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle);
+    method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
+    method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
+    method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
     method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
-    method public void setUpdateAvailable(String, boolean);
-    method public boolean updateIntentVerificationStatusAsUser(String, int, int);
-    method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
-    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+    method @Deprecated public void setUpdateAvailable(String, boolean);
+    method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
+    method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
+    method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
   }
 
 }
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 35f076f..9ed0108 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -6,13 +6,13 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
-    method public void clearCrossProfileIntentFilters(int);
-    method public int getInstallReason(String, android.os.UserHandle);
-    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
-    method public String[] getNamesForUids(int[]);
-    method @NonNull public String getServicesSystemSharedLibraryPackageName();
-    method @NonNull public String getSharedSystemSharedLibraryPackageName();
+    method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+    method @Deprecated public void clearCrossProfileIntentFilters(int);
+    method @Deprecated public int getInstallReason(String, android.os.UserHandle);
+    method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @Deprecated public String[] getNamesForUids(int[]);
+    method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName();
+    method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();
   }
 
 }
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 158e065..dc55dd2 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -237,6 +237,12 @@
      */
     private transient LinkAddress[] mLinkAddresses6;
     /**
+     * For hidden lists, getters, setters and adders will be hidden.
+     * @hide
+     */
+    private @NonNull List<LinkAddress> mLinkAddresses7 = new ArrayList<>();
+
+    /**
      * When using transient fields for caching it's often also a good idea to initialize them
      * lazily.
      *
@@ -486,6 +492,8 @@
      *   Making a field public will suppress getter generation in favor of accessing it directly.
      * @param linkAddresses5
      *   Final fields suppress generating a setter (when setters are requested).
+     * @param linkAddresses7
+     *   For hidden lists, getters, setters and adders will be hidden.
      * @param stringRes
      *   Fields with certain annotations are automatically validated in constructor
      *
@@ -529,9 +537,10 @@
             @State int state,
             @NonNull CharSequence charSeq,
             @Nullable LinkAddress[] linkAddresses5,
+            @NonNull List<LinkAddress> linkAddresses7,
             @StringRes int stringRes,
             @android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
-            @Size(2) @NonNull @FloatRange(from = 0f) float[] coords,
+            @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] coords,
             @NonNull IBinder token,
             @Nullable ICompanionDeviceManager iPCInterface) {
         this.mNum = num;
@@ -595,6 +604,9 @@
         AnnotationValidations.validate(
                 NonNull.class, null, charSeq);
         this.mLinkAddresses5 = linkAddresses5;
+        this.mLinkAddresses7 = linkAddresses7;
+        AnnotationValidations.validate(
+                NonNull.class, null, mLinkAddresses7);
         this.mStringRes = stringRes;
         AnnotationValidations.validate(
                 StringRes.class, null, mStringRes);
@@ -609,13 +621,11 @@
                 "value", 2);
         AnnotationValidations.validate(
                 NonNull.class, null, mCoords);
-        int coordsSize = mCoords.length;
-        for (int i = 0; i < coordsSize; i++) {
-            AnnotationValidations.validate(
-                    FloatRange.class, null, mCoords[i],
-                    "from", 0f);
-        }
-
+        AnnotationValidations.validate(
+                Each.class, null, mCoords);
+        AnnotationValidations.validate(
+                FloatRange.class, null, mCoords,
+                "from", 0f);
         this.mToken = token;
         AnnotationValidations.validate(
                 NonNull.class, null, mToken);
@@ -777,6 +787,16 @@
     }
 
     /**
+     * For hidden lists, getters, setters and adders will be hidden.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<LinkAddress> getLinkAddresses7() {
+        return mLinkAddresses7;
+    }
+
+    /**
      * Fields with certain annotations are automatically validated in constructor
      *
      * You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -815,7 +835,7 @@
      * @see AnnotationValidations#validate(Class, Size, int, String, int)
      */
     @DataClass.Generated.Member
-    public @Size(2) @NonNull @FloatRange(from = 0f) float[] getCoords() {
+    public @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] getCoords() {
         return mCoords;
     }
 
@@ -1065,6 +1085,19 @@
     }
 
     /**
+     * For hidden lists, getters, setters and adders will be hidden.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @NonNull SampleDataClass setLinkAddresses7(@NonNull List<LinkAddress> value) {
+        mLinkAddresses7 = value;
+        AnnotationValidations.validate(
+                NonNull.class, null, mLinkAddresses7);
+        return this;
+    }
+
+    /**
      * Fields with certain annotations are automatically validated in constructor
      *
      * You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1111,20 +1144,18 @@
      * @see AnnotationValidations#validate(Class, Size, int, String, int)
      */
     @DataClass.Generated.Member
-    public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+    public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
         mCoords = value;
         AnnotationValidations.validate(
                 Size.class, null, mCoords.length,
                 "value", 2);
         AnnotationValidations.validate(
                 NonNull.class, null, mCoords);
-        int coordsSize = mCoords.length;
-        for (int i = 0; i < coordsSize; i++) {
-            AnnotationValidations.validate(
-                    FloatRange.class, null, mCoords[i],
-                    "from", 0f);
-        }
-
+        AnnotationValidations.validate(
+                Each.class, null, mCoords);
+        AnnotationValidations.validate(
+                FloatRange.class, null, mCoords,
+                "from", 0f);
         return this;
     }
 
@@ -1172,6 +1203,7 @@
                 "state = " + stateToString(mState) + ", " +
                 "charSeq = " + charSeq + ", " +
                 "linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
+                "linkAddresses7 = " + mLinkAddresses7 + ", " +
                 "stringRes = " + mStringRes + ", " +
                 "dayOfWeek = " + mDayOfWeek + ", " +
                 "coords = " + java.util.Arrays.toString(mCoords) + ", " +
@@ -1210,6 +1242,7 @@
                 && mState == that.mState
                 && Objects.equals(charSeq, that.charSeq)
                 && java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
+                && Objects.equals(mLinkAddresses7, that.mLinkAddresses7)
                 && mStringRes == that.mStringRes
                 && mDayOfWeek == that.mDayOfWeek
                 && java.util.Arrays.equals(mCoords, that.mCoords)
@@ -1241,6 +1274,7 @@
         _hash = 31 * _hash + mState;
         _hash = 31 * _hash + Objects.hashCode(charSeq);
         _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5);
+        _hash = 31 * _hash + Objects.hashCode(mLinkAddresses7);
         _hash = 31 * _hash + mStringRes;
         _hash = 31 * _hash + mDayOfWeek;
         _hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
@@ -1270,6 +1304,7 @@
         actionInt.acceptInt(this, "state", mState);
         actionObject.acceptObject(this, "charSeq", charSeq);
         actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+        actionObject.acceptObject(this, "linkAddresses7", mLinkAddresses7);
         actionInt.acceptInt(this, "stringRes", mStringRes);
         actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
         actionObject.acceptObject(this, "coords", mCoords);
@@ -1298,6 +1333,7 @@
         action.acceptObject(this, "state", mState);
         action.acceptObject(this, "charSeq", charSeq);
         action.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+        action.acceptObject(this, "linkAddresses7", mLinkAddresses7);
         action.acceptObject(this, "stringRes", mStringRes);
         action.acceptObject(this, "dayOfWeek", mDayOfWeek);
         action.acceptObject(this, "coords", mCoords);
@@ -1338,7 +1374,7 @@
         if (mOtherParcelable != null) flg |= 0x40;
         if (mLinkAddresses4 != null) flg |= 0x800;
         if (mLinkAddresses5 != null) flg |= 0x10000;
-        if (mIPCInterface != null) flg |= 0x200000;
+        if (mIPCInterface != null) flg |= 0x400000;
         dest.writeLong(flg);
         dest.writeInt(mNum);
         dest.writeInt(mNum2);
@@ -1357,6 +1393,7 @@
         dest.writeInt(mState);
         dest.writeCharSequence(charSeq);
         if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
+        dest.writeParcelableList(mLinkAddresses7, flags);
         dest.writeInt(mStringRes);
         dest.writeInt(mDayOfWeek);
         dest.writeFloatArray(mCoords);
@@ -1395,11 +1432,13 @@
         int state = in.readInt();
         CharSequence _charSeq = (CharSequence) in.readCharSequence();
         LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+        List<LinkAddress> linkAddresses7 = new ArrayList<>();
+        in.readParcelableList(linkAddresses7, LinkAddress.class.getClassLoader());
         int stringRes = in.readInt();
         int dayOfWeek = in.readInt();
         float[] coords = in.createFloatArray();
         IBinder token = (IBinder) in.readStrongBinder();
-        ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
+        ICompanionDeviceManager iPCInterface = (flg & 0x400000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
 
         this.mNum = num;
         this.mNum2 = num2;
@@ -1462,6 +1501,9 @@
         AnnotationValidations.validate(
                 NonNull.class, null, charSeq);
         this.mLinkAddresses5 = linkAddresses5;
+        this.mLinkAddresses7 = linkAddresses7;
+        AnnotationValidations.validate(
+                NonNull.class, null, mLinkAddresses7);
         this.mStringRes = stringRes;
         AnnotationValidations.validate(
                 StringRes.class, null, mStringRes);
@@ -1476,13 +1518,11 @@
                 "value", 2);
         AnnotationValidations.validate(
                 NonNull.class, null, mCoords);
-        int coordsSize = mCoords.length;
-        for (int i = 0; i < coordsSize; i++) {
-            AnnotationValidations.validate(
-                    FloatRange.class, null, mCoords[i],
-                    "from", 0f);
-        }
-
+        AnnotationValidations.validate(
+                Each.class, null, mCoords);
+        AnnotationValidations.validate(
+                FloatRange.class, null, mCoords,
+                "from", 0f);
         this.mToken = token;
         AnnotationValidations.validate(
                 NonNull.class, null, mToken);
@@ -1529,9 +1569,10 @@
         private @State int mState;
         private @NonNull CharSequence charSeq;
         private @Nullable LinkAddress[] mLinkAddresses5;
+        private @NonNull List<LinkAddress> mLinkAddresses7;
         private @StringRes int mStringRes;
         private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
-        private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords;
+        private @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] mCoords;
         private @NonNull IBinder mToken;
         private @Nullable ICompanionDeviceManager mIPCInterface;
 
@@ -1823,6 +1864,30 @@
         }
 
         /**
+         * For hidden lists, getters, setters and adders will be hidden.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setLinkAddresses7(@NonNull List<LinkAddress> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20000;
+            mLinkAddresses7 = value;
+            return this;
+        }
+
+        /** @see #setLinkAddresses7 @hide */
+        @DataClass.Generated.Member
+        public @NonNull Builder addLinkAddresses7(@NonNull LinkAddress value) {
+            // You can refine this method's name by providing item's singular name, e.g.:
+            // @DataClass.PluralOf("item")) mItems = ...
+
+            if (mLinkAddresses7 == null) setLinkAddresses7(new ArrayList<>());
+            mLinkAddresses7.add(value);
+            return this;
+        }
+
+        /**
          * Fields with certain annotations are automatically validated in constructor
          *
          * You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1837,7 +1902,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setStringRes(@StringRes int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20000;
+            mBuilderFieldsSet |= 0x40000;
             mStringRes = value;
             return this;
         }
@@ -1852,7 +1917,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x40000;
+            mBuilderFieldsSet |= 0x80000;
             mDayOfWeek = value;
             return this;
         }
@@ -1867,9 +1932,9 @@
          * @see AnnotationValidations#validate(Class, Size, int, String, int)
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+        public @NonNull Builder setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x80000;
+            mBuilderFieldsSet |= 0x100000;
             mCoords = value;
             return this;
         }
@@ -1880,7 +1945,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setToken(@NonNull IBinder value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x100000;
+            mBuilderFieldsSet |= 0x200000;
             mToken = value;
             return this;
         }
@@ -1891,7 +1956,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x200000;
+            mBuilderFieldsSet |= 0x400000;
             mIPCInterface = value;
             return this;
         }
@@ -1899,7 +1964,7 @@
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull SampleDataClass build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400000; // Mark builder used
+            mBuilderFieldsSet |= 0x800000; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mName2 = "Bob";
@@ -1935,18 +2000,21 @@
                 charSeq = "";
             }
             if ((mBuilderFieldsSet & 0x20000) == 0) {
-                mStringRes = 0;
+                mLinkAddresses7 = new ArrayList<>();
             }
             if ((mBuilderFieldsSet & 0x40000) == 0) {
-                mDayOfWeek = 3;
+                mStringRes = 0;
             }
             if ((mBuilderFieldsSet & 0x80000) == 0) {
-                mCoords = new float[] { 0f, 0f };
+                mDayOfWeek = 3;
             }
             if ((mBuilderFieldsSet & 0x100000) == 0) {
-                mToken = new Binder();
+                mCoords = new float[] { 0f, 0f };
             }
             if ((mBuilderFieldsSet & 0x200000) == 0) {
+                mToken = new Binder();
+            }
+            if ((mBuilderFieldsSet & 0x400000) == 0) {
                 mIPCInterface = null;
             }
             SampleDataClass o = new SampleDataClass(
@@ -1967,6 +2035,7 @@
                     mState,
                     charSeq,
                     mLinkAddresses5,
+                    mLinkAddresses7,
                     mStringRes,
                     mDayOfWeek,
                     mCoords,
@@ -1976,7 +2045,7 @@
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400000) != 0) {
+            if ((mBuilderFieldsSet & 0x800000) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -1984,10 +2053,10 @@
     }
 
     @DataClass.Generated(
-            time = 1616541539978L,
+            time = 1697693846352L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
-            inputSignatures = "public static final  java.lang.String STATE_NAME_UNDEFINED\npublic static final  java.lang.String STATE_NAME_ON\npublic static final  java.lang.String STATE_NAME_OFF\npublic static final  int STATE_ON\npublic static final  int STATE_OFF\npublic static final  int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate  int mNum\nprivate  int mNum2\nprivate  int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient  android.net.LinkAddress[] mLinkAddresses6\ntransient  int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static  java.lang.String defaultName4()\nprivate  int[] lazyInitTmpStorage()\npublic  android.net.LinkAddress[] getLinkAddresses4()\nprivate  boolean patternEquals(java.util.regex.Pattern)\nprivate  int patternHashCode()\nprivate  void onConstructed()\npublic  void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+            inputSignatures = "public static final  java.lang.String STATE_NAME_UNDEFINED\npublic static final  java.lang.String STATE_NAME_ON\npublic static final  java.lang.String STATE_NAME_OFF\npublic static final  int STATE_ON\npublic static final  int STATE_OFF\npublic static final  int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate  int mNum\nprivate  int mNum2\nprivate  int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient  android.net.LinkAddress[] mLinkAddresses6\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses7\ntransient  int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static  java.lang.String defaultName4()\nprivate  int[] lazyInitTmpStorage()\npublic  android.net.LinkAddress[] getLinkAddresses4()\nprivate  boolean patternEquals(java.util.regex.Pattern)\nprivate  int patternHashCode()\nprivate  void onConstructed()\npublic  void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
     @Deprecated
     private void __metadata() {}
 
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 79348d1..ae7c2a9 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -247,7 +247,7 @@
 
             int rc = 0;
             try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
-                transaction.setFrameRateCategory(mSurfaceControl, category);
+                transaction.setFrameRateCategory(mSurfaceControl, category, false);
                 transaction.apply();
             }
             return rc;
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 82aa85d..f4f2be6 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -53,7 +53,17 @@
 }
 
 filegroup {
-    name: "FlickerTestsAppLaunch-src",
+    name: "FlickerTestsAppLaunchCommon-src",
+    srcs: ["src/**/launch/common/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch1-src",
+    srcs: ["src/**/launch/OpenApp*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch2-src",
     srcs: ["src/**/launch/*.kt"],
 }
 
@@ -119,7 +129,8 @@
     exclude_srcs: [
         ":FlickerTestsAppClose-src",
         ":FlickerTestsIme-src",
-        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsAppLaunch1-src",
+        ":FlickerTestsAppLaunch2-src",
         ":FlickerTestsQuickswitch-src",
         ":FlickerTestsRotation-src",
         ":FlickerTestsNotification-src",
@@ -162,7 +173,8 @@
     instrumentation_target_package: "com.android.server.wm.flicker.launch",
     srcs: [
         ":FlickerTestsBase-src",
-        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch2-src",
     ],
     exclude_srcs: [
         ":FlickerTestsActivityEmbedding-src",
@@ -170,6 +182,39 @@
 }
 
 android_test {
+    name: "FlickerTestsAppLaunch1",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch1-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppLaunch2",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch2-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+        ":FlickerTestsAppLaunch1-src",
+    ],
+}
+
+android_test {
     name: "FlickerTestsNotification",
     defaults: ["FlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestNotification.xml"],
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 63acddf..0f47980 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -61,9 +61,7 @@
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6600s"/>
         <option name="hidden-api-checks" value="false"/>
-        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
         <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        -->
         <!-- PerfettoListener related arguments -->
         <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
         <option name="instrumentation-arg"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 359845d..47d6d23 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -36,7 +36,8 @@
 /**
  * Test launching a secondary Activity into Picture-In-Picture mode.
  *
- * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom
  * right corner on screen.
  *
  * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
@@ -63,7 +64,16 @@
         }
     }
 
-    /** Main and secondary activity start from a split each taking half of the screen. */
+    /**
+     * We expect the background layer to be visible during this transition.
+     */
+    @Presubmit
+    @Test
+    override fun backgroundLayerNeverVisible(): Unit {}
+
+    /**
+     * Main and secondary activity start from a split each taking half of the screen.
+     */
     @Presubmit
     @Test
     fun layersStartFromEqualSplit() {
@@ -109,7 +119,7 @@
                 .isVisible(TRANSITION_SNAPSHOT)
                 .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
                 .then()
-                .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true)
         }
         flicker.assertLayersEnd {
             visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 48d5041..f788efa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.launch.common.OpenAppFromIconTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,28 +52,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
-    OpenAppFromLauncherTransition(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                if (flicker.scenario.isTablet) {
-                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-                } else {
-                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
-                }
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-            }
-            transitions {
-                tapl
-                    .goHome()
-                    .switchToAllApps()
-                    .getAppIcon(testApp.appName)
-                    .launch(testApp.packageName)
-            }
-            teardown { testApp.exit(wmHelper) }
-        }
+    OpenAppFromIconTransition(flicker) {
 
     @FlakyTest(bugId = 240916028)
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index f575fcc..d86dc50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 93d0520..be07053 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -25,6 +25,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 78b58f4..f66eff9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 3f931c4..65214764 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.launch.common.OpenAppFromLockscreenTransition
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index b85362a..4d31c28 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -25,6 +25,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index 1bdb6e71..42e34b3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -30,6 +30,7 @@
 import android.view.KeyEvent
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
deleted file mode 100644
index 3d9c067..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
- *
- * Actions:
- * ```
- *     Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
- *     by clicking it's icon on all apps, and wait for transfer splash screen complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Verify no flickering when transfer splash screen to app window.
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
-    OpenAppFromIconColdTest(flicker) {
-    override val testApp = TransferSplashscreenAppHelper(instrumentation)
-
-    /**
-     * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
-     * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
-     * visible until the end
-     */
-    @Presubmit
-    @Test
-    fun appWindowAfterSplash() {
-        flicker.assertWm {
-            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
-                .then()
-                .isAppWindowOnTop(testApp)
-                .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
new file mode 100644
index 0000000..c854701
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+
+abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                if (flicker.scenario.isTablet) {
+                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+                } else {
+                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+                }
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+            }
+            transitions {
+                tapl
+                    .goHome()
+                    .switchToAllApps()
+                    .getAppIcon(testApp.appName)
+                    .launch(testApp.packageName)
+            }
+            teardown { testApp.exit(wmHelper) }
+        }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
similarity index 95%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
index 4fc9bcb..9d7096e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index cc501e6..7b08843 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
similarity index 98%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
index bb11be5..989619e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
new file mode 100644
index 0000000..2e9620b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ *
+ * Actions:
+ * ```
+ *     Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
+ *     by clicking it's icon on all apps, and wait for transfer splash screen complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Verify no flickering when transfer splash screen to app window.
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+    OpenAppFromIconTransition(flicker) {
+    override val testApp = TransferSplashscreenAppHelper(instrumentation)
+
+    /**
+     * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+     * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
+     * visible until the end
+     */
+    @Presubmit
+    @Test
+    fun appWindowAfterSplash() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+                .then()
+                .isAppWindowOnTop(testApp)
+                .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+        }
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() {
+        super.appWindowReplacesLauncherAsTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowAsTopWindowAtEnd() {
+        super.appWindowAsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesTopWindow() {
+        super.appWindowBecomesTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesVisible() {
+        super.appWindowBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowIsTopWindowAtEnd() {
+        super.appWindowIsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerBecomesVisible() {
+        super.appLayerBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerReplacesLauncher() {
+        super.appLayerReplacesLauncher()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun cujCompleted() {
+        super.cujCompleted()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index 49cbde0..d2537f6 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -24,7 +24,7 @@
     <!-- This test requires root to write against block device. -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="FsVerityTestApp.apk"/>
         <option name="cleanup-apks" value="true"/>
     </target_preparer>
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index fa86e9c..44de6a6 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -100,6 +100,7 @@
         const val RECEIVER_NAME = "DummyReceiver"
         private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
         private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
+        private const val GERMAN_LAYOUT_NAME = "keyboard_layout_german"
         private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
         const val LAYOUT_TYPE_QWERTZ = 2
         const val LAYOUT_TYPE_QWERTY = 1
@@ -108,6 +109,7 @@
 
     private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
     private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME)
+    private val GERMAN_LAYOUT_DESCRIPTOR = createLayoutDescriptor(GERMAN_LAYOUT_NAME)
     private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
         createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
 
@@ -710,7 +712,7 @@
             assertCorrectLayout(
                 keyboardDevice,
                 createImeSubtypeForLanguageTag("de"),
-                createLayoutDescriptor("keyboard_layout_german")
+                GERMAN_LAYOUT_DESCRIPTOR
             )
             assertCorrectLayout(
                 keyboardDevice,
@@ -775,13 +777,13 @@
             assertCorrectLayout(
                 keyboardDevice,
                 createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
-                createLayoutDescriptor("keyboard_layout_german")
+                GERMAN_LAYOUT_DESCRIPTOR
             )
             // Wrong layout type should match with language if provided layout type not available
             assertCorrectLayout(
                 keyboardDevice,
                 createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
-                createLayoutDescriptor("keyboard_layout_german")
+                GERMAN_LAYOUT_DESCRIPTOR
             )
             assertCorrectLayout(
                 keyboardDevice,
@@ -856,7 +858,7 @@
                         ArgumentMatchers.eq(createByteArray(
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                 LAYOUT_TYPE_DEFAULT,
-                                "German",
+                                GERMAN_LAYOUT_NAME,
                                 KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
                                 "de-Latn",
                                 LAYOUT_TYPE_QWERTZ))
@@ -882,7 +884,7 @@
                         ArgumentMatchers.eq(createByteArray(
                                 "en",
                                 LAYOUT_TYPE_QWERTY,
-                                "English (US)",
+                                ENGLISH_US_LAYOUT_NAME,
                                 KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
                                 "de-Latn",
                                 LAYOUT_TYPE_QWERTZ))
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index b39c932..33ff09b 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -113,7 +113,7 @@
         )
         val event = builder.addLayoutSelection(
             createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
-            KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
+            "English(US)(Qwerty)",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
         ).addLayoutSelection(
             createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
@@ -121,7 +121,7 @@
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
         ).addLayoutSelection(
             createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
-            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            "German",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).setIsFirstTimeConfiguration(true).build()
 
@@ -184,7 +184,7 @@
         )
         val event = builder.addLayoutSelection(
             createImeSubtype(4, null, "qwerty"), // Default language tag
-            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            "German",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).build()
 
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
index 56ab755..7e43566 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -37,6 +37,8 @@
     private var window: Window? = null
     private var currentModeDisplay: TextView? = null
 
+    private var desiredRatio = 0.0f
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         val window = window ?: throw IllegalStateException("Failed to attach window")
@@ -67,6 +69,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
+        desiredRatio = window?.desiredHdrHeadroom ?: 0.0f
         val hdrVis = if (display.isHdrSdrRatioAvailable) {
             display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener)
             View.VISIBLE
@@ -83,6 +86,11 @@
     }
 
     private fun setColorMode(newMode: Int) {
+        if (newMode == ActivityInfo.COLOR_MODE_HDR &&
+                window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) {
+            desiredRatio = (desiredRatio + 1) % 5.0f
+            window!!.desiredHdrHeadroom = desiredRatio
+        }
         window!!.colorMode = newMode
         updateModeInfoDisplay()
     }
diff --git a/tests/notification/Android.bp b/tests/notification/Android.bp
deleted file mode 100644
index 1c1b5a2..0000000
--- a/tests/notification/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "NotificationTests",
-    // Include all test java files.
-    srcs: ["src/**/*.java"],
-    libs: ["android.test.runner.stubs"],
-    sdk_version: "21",
-}
diff --git a/tests/notification/OWNERS b/tests/notification/OWNERS
deleted file mode 100644
index 396fd12..0000000
--- a/tests/notification/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/notification/OWNERS
diff --git a/tests/notification/res/drawable-nodpi/arubin_hed.jpeg b/tests/notification/res/drawable-nodpi/arubin_hed.jpeg
deleted file mode 100644
index c6d8ae9..0000000
--- a/tests/notification/res/drawable-nodpi/arubin_hed.jpeg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/bucket.png b/tests/notification/res/drawable-nodpi/bucket.png
deleted file mode 100644
index c865649..0000000
--- a/tests/notification/res/drawable-nodpi/bucket.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/matias_hed.jpg b/tests/notification/res/drawable-nodpi/matias_hed.jpg
deleted file mode 100644
index 8cc3081..0000000
--- a/tests/notification/res/drawable-nodpi/matias_hed.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/page_hed.jpg b/tests/notification/res/drawable-nodpi/page_hed.jpg
deleted file mode 100644
index ea950c8..0000000
--- a/tests/notification/res/drawable-nodpi/page_hed.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/romainguy_hed.jpg b/tests/notification/res/drawable-nodpi/romainguy_hed.jpg
deleted file mode 100644
index 5b7643e..0000000
--- a/tests/notification/res/drawable-nodpi/romainguy_hed.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg b/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg
deleted file mode 100644
index 68473ba..0000000
--- a/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/add.png b/tests/notification/res/drawable-xhdpi/add.png
deleted file mode 100644
index 7226b3d..0000000
--- a/tests/notification/res/drawable-xhdpi/add.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png b/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png
deleted file mode 100644
index ca20a91..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_end_call.png b/tests/notification/res/drawable-xhdpi/ic_end_call.png
deleted file mode 100644
index c464a6d..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_end_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_media_next.png b/tests/notification/res/drawable-xhdpi/ic_media_next.png
deleted file mode 100644
index 4def965..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_media_next.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_menu_upload.png b/tests/notification/res/drawable-xhdpi/ic_menu_upload.png
deleted file mode 100644
index f1438ed..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_menu_upload.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/icon.png b/tests/notification/res/drawable-xhdpi/icon.png
deleted file mode 100644
index 189e85b..0000000
--- a/tests/notification/res/drawable-xhdpi/icon.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png b/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png
deleted file mode 100644
index 658d04f..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png b/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png
deleted file mode 100644
index 5ae7782..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_email.png b/tests/notification/res/drawable-xhdpi/stat_notify_email.png
deleted file mode 100644
index 23c4672..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_email.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png b/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png
deleted file mode 100644
index 8719eff..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_sms.png b/tests/notification/res/drawable-xhdpi/stat_notify_sms.png
deleted file mode 100644
index 323cb3d..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_sms.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png b/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png
deleted file mode 100644
index 26dcda35..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png b/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png
deleted file mode 100644
index b8b2f8a..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png b/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png
deleted file mode 100644
index 12cae9f..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png b/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png
deleted file mode 100644
index db42b7c..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/layout/full_screen.xml b/tests/notification/res/layout/full_screen.xml
deleted file mode 100644
index 6ff7552..0000000
--- a/tests/notification/res/layout/full_screen.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ImageView
-        android:layout_height="match_parent"
-        android:layout_width="match_parent"
-        android:src="@drawable/page_hed"
-        android:onClick="dismiss"
-        />
-</FrameLayout>
\ No newline at end of file
diff --git a/tests/notification/res/layout/main.xml b/tests/notification/res/layout/main.xml
deleted file mode 100644
index f5a740f..0000000
--- a/tests/notification/res/layout/main.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    >
-    <LinearLayout android:id="@+id/linearLayout1" android:orientation="vertical" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_width="match_parent" android:layout_margin="35dp">
-        <Button android:id="@+id/button1" android:text="@string/post_button_label" android:layout_height="wrap_content" android:layout_width="match_parent" android:onClick="doPost"></Button>
-        <Button android:id="@+id/button2" android:text="@string/remove_button_label" android:layout_height="wrap_content" android:layout_width="match_parent" android:onClick="doRemove"></Button>
-    </LinearLayout>
-</FrameLayout>
diff --git a/tests/notification/res/values/dimens.xml b/tests/notification/res/values/dimens.xml
deleted file mode 100644
index 21e7bc3..0000000
--- a/tests/notification/res/values/dimens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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>
-    <!-- The width of the big icons in notifications. -->
-    <dimen name="notification_large_icon_width">64dp</dimen>
-    <!-- The width of the big icons in notifications. -->
-    <dimen name="notification_large_icon_height">64dp</dimen>
-</resources>
diff --git a/tests/notification/res/values/strings.xml b/tests/notification/res/values/strings.xml
deleted file mode 100644
index 80bf103..0000000
--- a/tests/notification/res/values/strings.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <string name="hello">Hello World, NotificationShowcaseActivity!</string>
-    <string name="app_name">NotificationShowcase</string>
-    <string name="post_button_label">Post Notifications</string>
-    <string name="remove_button_label">Remove Notifications</string>
-    <string name="answered">call answered</string>
-    <string name="ignored">call ignored</string>
-    <string name="full_screen_name">Full Screen Activity</string>
-</resources>
diff --git a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java b/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java
deleted file mode 100644
index 5d639f6..0000000
--- a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java
+++ /dev/null
@@ -1,494 +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 android.app;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcel;
-import android.os.SystemClock;
-import android.provider.ContactsContract;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.Suppress;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import java.lang.reflect.Method;
-import java.lang.InterruptedException;
-import java.lang.NoSuchMethodError;
-import java.lang.NoSuchMethodException;
-import java.util.ArrayList;
-
-import com.android.frameworks.tests.notification.R;
-
-public class NotificationTests extends AndroidTestCase {
-    private static final String TAG = "NOTEST";
-    public static void L(String msg, Object... args) {
-        Log.v(TAG, (args == null || args.length == 0) ? msg : String.format(msg, args));
-    }
-
-    public static final String ACTION_CREATE = "create";
-    public static final int NOTIFICATION_ID = 31338;
-
-    public static final boolean SHOW_PHONE_CALL = false;
-    public static final boolean SHOW_INBOX = true;
-    public static final boolean SHOW_BIG_TEXT = true;
-    public static final boolean SHOW_BIG_PICTURE = true;
-    public static final boolean SHOW_MEDIA = true;
-    public static final boolean SHOW_STOPWATCH = false;
-    public static final boolean SHOW_SOCIAL = false;
-    public static final boolean SHOW_CALENDAR = false;
-    public static final boolean SHOW_PROGRESS = false;
-
-    private static Bitmap getBitmap(Context context, int resId) {
-        int largeIconWidth = (int) context.getResources()
-                .getDimension(R.dimen.notification_large_icon_width);
-        int largeIconHeight = (int) context.getResources()
-                .getDimension(R.dimen.notification_large_icon_height);
-        Drawable d = context.getResources().getDrawable(resId);
-        Bitmap b = Bitmap.createBitmap(largeIconWidth, largeIconHeight, Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(b);
-        d.setBounds(0, 0, largeIconWidth, largeIconHeight);
-        d.draw(c);
-        return b;
-    }
-
-    private static PendingIntent makeEmailIntent(Context context, String who) {
-        final Intent intent = new Intent(android.content.Intent.ACTION_SENDTO,
-                Uri.parse("mailto:" + who));
-        return PendingIntent.getActivity(
-                context, 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
-    }
-
-    static final String[] LINES = new String[] {
-            "Uh oh",
-            "Getting kicked out of this room",
-            "I'll be back in 5-10 minutes.",
-            "And now \u2026 I have to find my shoes. \uD83D\uDC63",
-            "\uD83D\uDC5F \uD83D\uDC5F",
-            "\uD83D\uDC60 \uD83D\uDC60",
-    };
-    static final int MAX_LINES = 5;
-    public static Notification makeBigTextNotification(Context context, int update, int id,
-            long when) {
-        String personUri = null;
-        /*
-        Cursor c = null;
-        try {
-            String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY };
-            String selections = ContactsContract.Contacts.DISPLAY_NAME + " = 'Mike Cleron'";
-            final ContentResolver contentResolver = context.getContentResolver();
-            c = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
-                    projection, selections, null, null);
-            if (c != null && c.getCount() > 0) {
-                c.moveToFirst();
-                int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
-                int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
-                String lookupKey = c.getString(lookupIdx);
-                long contactId = c.getLong(idIdx);
-                Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
-                personUri = lookupUri.toString();
-            }
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        if (TextUtils.isEmpty(personUri)) {
-            Log.w(TAG, "failed to find contact for Mike Cleron");
-        } else {
-            Log.w(TAG, "Mike Cleron is " + personUri);
-        }
-        */
-
-        StringBuilder longSmsText = new StringBuilder();
-        int end = 2 + update;
-        if (end > LINES.length) {
-            end = LINES.length;
-        }
-        final int start = Math.max(0, end - MAX_LINES);
-        for (int i=start; i<end; i++) {
-            if (i >= LINES.length) break;
-            if (i > start) longSmsText.append("\n");
-            longSmsText.append(LINES[i]);
-        }
-        if (update > 2) {
-            when = System.currentTimeMillis();
-        }
-        Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle()
-                .bigText(longSmsText);
-        Notification bigText = new Notification.Builder(context)
-                .setContentTitle("Mike Cleron")
-                .setContentIntent(ToastService.getPendingIntent(context, "Clicked on bigText"))
-                .setContentText(longSmsText)
-                        //.setTicker("Mike Cleron: " + longSmsText)
-                .setWhen(when)
-                .setLargeIcon(getBitmap(context, R.drawable.bucket))
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setNumber(update)
-                .setSmallIcon(R.drawable.stat_notify_talk_text)
-                .setStyle(bigTextStyle)
-                .setDefaults(Notification.DEFAULT_SOUND)
-                .addPerson(personUri)
-                .build();
-        return bigText;
-    }
-
-    public static Notification makeUploadNotification(Context context, int progress, long when) {
-        Notification.Builder uploadNotification = new Notification.Builder(context)
-                .setContentTitle("File Upload")
-                .setContentText("foo.txt")
-                .setPriority(Notification.PRIORITY_MIN)
-                .setContentIntent(ToastService.getPendingIntent(context, "Clicked on Upload"))
-                .setWhen(when)
-                .setSmallIcon(R.drawable.ic_menu_upload)
-                .setProgress(100, Math.min(progress, 100), false);
-        return uploadNotification.build();
-    }
-
-    static SpannableStringBuilder BOLD(CharSequence str) {
-        final SpannableStringBuilder ssb = new SpannableStringBuilder(str);
-        ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0);
-        return ssb;
-    }
-
-    public static class ToastService extends IntentService {
-
-        private static final String TAG = "ToastService";
-
-        private static final String ACTION_TOAST = "toast";
-
-        private Handler handler;
-
-        public ToastService() {
-            super(TAG);
-        }
-        public ToastService(String name) {
-            super(name);
-        }
-
-        @Override
-        public int onStartCommand(Intent intent, int flags, int startId) {
-            handler = new Handler();
-            return super.onStartCommand(intent, flags, startId);
-        }
-
-        @Override
-        protected void onHandleIntent(Intent intent) {
-            Log.v(TAG, "clicked a thing! intent=" + intent.toString());
-            if (intent.hasExtra("text")) {
-                final String text = intent.getStringExtra("text");
-                handler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show();
-                        Log.v(TAG, "toast " + text);
-                    }
-                });
-            }
-        }
-
-        public static PendingIntent getPendingIntent(Context context, String text) {
-            Intent toastIntent = new Intent(context, ToastService.class);
-            toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message
-            toastIntent.putExtra("text", text);
-            PendingIntent pi = PendingIntent.getService(
-                    context, 58, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-            return pi;
-        }
-    }
-
-    public static void sleepIfYouCan(int ms) {
-        try {
-            Thread.sleep(ms);
-        } catch (InterruptedException e) {}
-    }
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public static String summarize(Notification n) {
-        return String.format("<notif title=\"%s\" icon=0x%08x view=%s>",
-                n.extras.get(Notification.EXTRA_TITLE),
-                n.icon,
-                String.valueOf(n.contentView));
-    }
-    
-    public void testCreate() throws Exception {
-        ArrayList<Notification> mNotifications = new ArrayList<Notification>();
-        NotificationManager noMa =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-
-        L("Constructing notifications...");
-        if (SHOW_BIG_TEXT) {
-            int bigtextId = mNotifications.size();
-            final long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = makeBigTextNotification(mContext, 0, bigtextId, System.currentTimeMillis());
-            L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(n);
-        }
-
-        int uploadId = mNotifications.size();
-        long uploadWhen = System.currentTimeMillis();
-
-        if (SHOW_PROGRESS) {
-            mNotifications.add(makeUploadNotification(mContext, 0, uploadWhen));
-        }
-
-        if (SHOW_PHONE_CALL) {
-            int phoneId = mNotifications.size();
-            final PendingIntent fullscreenIntent
-                    = FullScreenActivity.getPendingIntent(mContext, phoneId);
-            final long time = SystemClock.currentThreadTimeMillis();
-            Notification phoneCall = new Notification.Builder(mContext)
-                    .setContentTitle("Incoming call")
-                    .setContentText("Matias Duarte")
-                    .setLargeIcon(getBitmap(mContext, R.drawable.matias_hed))
-                    .setSmallIcon(R.drawable.stat_sys_phone_call)
-                    .setDefaults(Notification.DEFAULT_SOUND)
-                    .setPriority(Notification.PRIORITY_MAX)
-                    .setContentIntent(fullscreenIntent)
-                    .setFullScreenIntent(fullscreenIntent, true)
-                    .addAction(R.drawable.ic_dial_action_call, "Answer",
-                            ToastService.getPendingIntent(mContext, "Clicked on Answer"))
-                    .addAction(R.drawable.ic_end_call, "Ignore",
-                            ToastService.getPendingIntent(mContext, "Clicked on Ignore"))
-                    .setOngoing(true)
-                    .addPerson(Uri.fromParts("tel", "1 (617) 555-1212", null).toString())
-                    .build();
-            L("  %s: create=%dms", phoneCall.toString(), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(phoneCall);
-        }
-
-        if (SHOW_STOPWATCH) {
-            final long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = new Notification.Builder(mContext)
-                    .setContentTitle("Stopwatch PRO")
-                    .setContentText("Counting up")
-                    .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Stopwatch"))
-                    .setSmallIcon(R.drawable.stat_notify_alarm)
-                    .setUsesChronometer(true)
-                    .build();
-            L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(n);
-        }
-
-        if (SHOW_CALENDAR) {
-            final long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = new Notification.Builder(mContext)
-                    .setContentTitle("J Planning")
-                    .setContentText("The Botcave")
-                    .setWhen(System.currentTimeMillis())
-                    .setSmallIcon(R.drawable.stat_notify_calendar)
-                    .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on calendar event"))
-                    .setContentInfo("7PM")
-                    .addAction(R.drawable.stat_notify_snooze, "+10 min",
-                            ToastService.getPendingIntent(mContext, "snoozed 10 min"))
-                    .addAction(R.drawable.stat_notify_snooze_longer, "+1 hour",
-                            ToastService.getPendingIntent(mContext, "snoozed 1 hr"))
-                    .addAction(R.drawable.stat_notify_email, "Email",
-                            ToastService.getPendingIntent(mContext,
-                                    "Congratulations, you just destroyed someone's inbox zero"))
-                    .build();
-            L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(n);
-        }
-
-        if (SHOW_BIG_PICTURE) {
-            BitmapDrawable d =
-                    (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.romainguy_rockaway);
-            final long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = new Notification.Builder(mContext)
-                        .setContentTitle("Romain Guy")
-                        .setContentText("I was lucky to find a Canon 5D Mk III at a local Bay Area "
-                                + "store last week but I had not been able to try it in the field "
-                                + "until tonight. After a few days of rain the sky finally cleared "
-                                + "up. Rockaway Beach did not disappoint and I was finally able to "
-                                + "see what my new camera feels like when shooting landscapes.")
-                        .setSmallIcon(android.R.drawable.stat_notify_chat)
-                        .setContentIntent(
-                                ToastService.getPendingIntent(mContext, "Clicked picture"))
-                        .setLargeIcon(getBitmap(mContext, R.drawable.romainguy_hed))
-                        .addAction(R.drawable.add, "Add to Gallery",
-                                ToastService.getPendingIntent(mContext, "Added"))
-                        .setStyle(new Notification.BigPictureStyle()
-                                .bigPicture(d.getBitmap()))
-                        .build();
-            L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(n);
-        }
-
-        if (SHOW_INBOX) {
-            final long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = new Notification.Builder(mContext)
-                    .setContentTitle("New mail")
-                    .setContentText("3 new messages")
-                    .setSubText("example@gmail.com")
-                    .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Mail"))
-                    .setSmallIcon(R.drawable.stat_notify_email)
-                    .setStyle(new Notification.InboxStyle()
-                                    .setSummaryText("example@gmail.com")
-                                    .addLine(BOLD("Alice:").append(" hey there!"))
-                                    .addLine(BOLD("Bob:").append(" hi there!"))
-                                    .addLine(BOLD("Charlie:").append(" Iz IN UR EMAILZ!!"))
-                    ).build();
-            L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(n);
-        }
-
-        if (SHOW_SOCIAL) {
-            final long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = new Notification.Builder(mContext)
-                    .setContentTitle("Social Network")
-                    .setContentText("You were mentioned in a post")
-                    .setContentInfo("example@gmail.com")
-                    .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Social"))
-                    .setSmallIcon(android.R.drawable.stat_notify_chat)
-                    .setPriority(Notification.PRIORITY_LOW)
-                    .build();
-            L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
-            mNotifications.add(n);
-        }
-
-        L("Posting notifications...");
-        for (int i=0; i<mNotifications.size(); i++) {
-            final int count = 4;
-            for (int j=0; j<count; j++) {
-                long time = SystemClock.currentThreadTimeMillis();
-                final Notification n = mNotifications.get(i);
-                noMa.notify(NOTIFICATION_ID + i, n);
-                time = SystemClock.currentThreadTimeMillis() - time;
-                L("  %s: notify=%dms (%d/%d)", summarize(n), time,
-                        j + 1, count);
-                sleepIfYouCan(150);
-            }
-        }
-
-        sleepIfYouCan(1000);
-
-        L("Canceling notifications...");
-        for (int i=0; i<mNotifications.size(); i++) {
-            final Notification n = mNotifications.get(i);
-            long time = SystemClock.currentThreadTimeMillis();
-            noMa.cancel(NOTIFICATION_ID + i);
-            time = SystemClock.currentThreadTimeMillis() - time;
-            L("  %s: cancel=%dms", summarize(n), time);
-        }
-
-        sleepIfYouCan(500);
-
-        L("Parceling notifications...");
-        // we want to be able to use this test on older OSes that do not have getOpenAshmemSize
-        Method getOpenAshmemSize = null;
-        try {
-            getOpenAshmemSize = Parcel.class.getMethod("getOpenAshmemSize");
-        } catch (NoSuchMethodException ex) {
-        }
-        for (int i=0; i<mNotifications.size(); i++) {
-            Parcel p = Parcel.obtain();
-            {
-                final Notification n = mNotifications.get(i);
-                long time = SystemClock.currentThreadTimeMillis();
-                n.writeToParcel(p, 0);
-                time = SystemClock.currentThreadTimeMillis() - time;
-                L("  %s: write parcel=%dms size=%d ashmem=%s",
-                        summarize(n), time, p.dataPosition(),
-                        (getOpenAshmemSize != null)
-                            ? getOpenAshmemSize.invoke(p)
-                            : "???");
-                p.setDataPosition(0);
-            }
-
-            long time = SystemClock.currentThreadTimeMillis();
-            final Notification n2 = Notification.CREATOR.createFromParcel(p);
-            time = SystemClock.currentThreadTimeMillis() - time;
-            L("  %s: parcel read=%dms", summarize(n2), time);
-
-            time = SystemClock.currentThreadTimeMillis();
-            noMa.notify(NOTIFICATION_ID + i, n2);
-            time = SystemClock.currentThreadTimeMillis() - time;
-            L("  %s: notify=%dms", summarize(n2), time);
-        }
-
-        sleepIfYouCan(500);
-
-        L("Canceling notifications...");
-        for (int i=0; i<mNotifications.size(); i++) {
-            long time = SystemClock.currentThreadTimeMillis();
-            final Notification n = mNotifications.get(i);
-            noMa.cancel(NOTIFICATION_ID + i);
-            time = SystemClock.currentThreadTimeMillis() - time;
-            L("  %s: cancel=%dms", summarize(n), time);
-        }
-
-
-//            if (SHOW_PROGRESS) {
-//                ProgressService.startProgressUpdater(this, uploadId, uploadWhen, 0);
-//            }
-    }
-
-    public static class FullScreenActivity extends Activity {
-        public static final String EXTRA_ID = "id";
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setContentView(R.layout.full_screen);
-            final Intent intent = getIntent();
-            if (intent != null && intent.hasExtra(EXTRA_ID)) {
-                final int id = intent.getIntExtra(EXTRA_ID, -1);
-                if (id >= 0) {
-                    NotificationManager noMa =
-                            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-                    noMa.cancel(NOTIFICATION_ID + id);
-                }
-            }
-        }
-
-        public void dismiss(View v) {
-            finish();
-        }
-
-        public static PendingIntent getPendingIntent(Context context, int id) {
-            Intent fullScreenIntent = new Intent(context, FullScreenActivity.class);
-            fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-            fullScreenIntent.putExtra(EXTRA_ID, id);
-            PendingIntent pi = PendingIntent.getActivity(
-                    context, 22, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-            return pi;
-        }
-    }
-}
-
diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
index 6a6ab00..a43e1b0 100644
--- a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
+++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
@@ -21,15 +21,39 @@
 
 public class TestableFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver {
     private Map<String, Boolean> mOverrides = new HashMap<>();
+    private Map<String, Integer> mOverridesInt = new HashMap<>();
+    private Map<String, String> mOverridesString = new HashMap<>();
 
     @Override
     public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) {
         return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue);
     }
 
+    @Override
+    public int getIntValue(SystemUiSystemPropertiesFlags.Flag flag) {
+        return mOverridesInt.getOrDefault(flag.mSysPropKey, flag.mDefaultIntValue);
+    }
+
+    @Override
+    public String getStringValue(SystemUiSystemPropertiesFlags.Flag flag) {
+        return mOverridesString.getOrDefault(flag.mSysPropKey, flag.mDefaultStringValue);
+    }
+
     public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
             boolean isEnabled) {
         mOverrides.put(flag.mSysPropKey, isEnabled);
         return this;
     }
+
+    public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
+        int value) {
+        mOverridesInt.put(flag.mSysPropKey, value);
+        return this;
+    }
+
+    public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
+        String value) {
+        mOverridesString.put(flag.mSysPropKey, value);
+        return this;
+    }
 }
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 1b1e93bd..a08f385 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -323,6 +323,7 @@
             "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_);
   }
 
   int Action(const std::vector<std::string>& args) override;
@@ -347,6 +348,7 @@
   std::optional<std::string> stable_id_file_path_;
   std::vector<std::string> split_args_;
   std::optional<std::string> trace_folder_;
+  std::vector<std::string> feature_flags_args_;
 };
 
 }// namespace aapt
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
index bfd3508..c901efa 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
+++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptAutoVersionTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp
index 7db9d26..d0649ea 100644
--- a/tools/aapt2/integration-tests/BasicTest/Android.bp
+++ b/tools/aapt2/integration-tests/BasicTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptBasicTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
index 80404ee..ebb4e9f 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
@@ -24,9 +24,9 @@
 }
 
 android_test {
-
     name: "AaptTestStaticLib_App",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     asset_dirs: [
         "assets",
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
index a84da43..ee12a929 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
@@ -26,6 +26,7 @@
 android_library {
     name: "AaptTestStaticLib_LibOne",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
 }
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
index d386c3a..83b23624 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
@@ -26,6 +26,7 @@
 android_library {
     name: "AaptTestStaticLib_LibTwo",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
     libs: ["AaptTestStaticLib_LibOne"],
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 1e8cf86..15a6a20 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptSymlinkTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 6857333..d3a8b03 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -327,7 +327,8 @@
             +"return$maybeCast this;"
         }
 
-        val javadocSeeSetter = "/** @see #$setterName */"
+        val javadocSeeSetter =
+                if (isHidden()) "/** @see #$setterName @hide */" else "/** @see #$setterName */"
         val adderName = "add$SingularName"
 
         val singularNameCustomizationHint = if (SingularNameOrNull == null) {
@@ -750,6 +751,15 @@
     }
 }
 
+fun FieldInfo.isHidden(): Boolean {
+    if (javadocFull != null) {
+        (javadocFull ?: "/**\n */").lines().forEach {
+            if (it.contains("@hide")) return true
+        }
+    }
+    return false
+}
+
 fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
     if (javadocFull != null || forceHide) {
         var hidden = false
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index a617876..226e2fad 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -7,9 +7,38 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+// Visibility only for ravenwood prototype uses.
+genrule_defaults {
+    name: "hoststubgen-for-prototype-only-genrule",
+    visibility: [
+        ":__subpackages__",
+        "//frameworks/base/ravenwood:__subpackages__",
+    ],
+}
+
+// Visibility only for ravenwood prototype uses.
+java_defaults {
+    name: "hoststubgen-for-prototype-only-java",
+    visibility: [
+        ":__subpackages__",
+        "//frameworks/base/ravenwood:__subpackages__",
+    ],
+}
+
+// Visibility only for ravenwood prototype uses.
+filegroup_defaults {
+    name: "hoststubgen-for-prototype-only-filegroup",
+    visibility: [
+        ":__subpackages__",
+        "//frameworks/base/ravenwood:__subpackages__",
+    ],
+}
+
 // This library contains the standard hoststubgen annotations.
+// This is only for the prototype. The productionized version is "ravenwood-annotations".
 java_library {
     name: "hoststubgen-annotations",
+    defaults: ["hoststubgen-for-prototype-only-java"],
     srcs: [
         "annotations-src/**/*.java",
     ],
@@ -18,7 +47,6 @@
     // Seems like we need it to avoid circular deps.
     // Copied it from "app-compat-annotations".
     sdk_version: "core_current",
-    visibility: ["//visibility:public"],
 }
 
 // This library contains helper classes used in the host side test environment at runtime.
@@ -30,11 +58,6 @@
     ],
     libs: [
         "junit",
-        "ow2-asm",
-        "ow2-asm-analysis",
-        "ow2-asm-commons",
-        "ow2-asm-tree",
-        "ow2-asm-util",
     ],
     static_libs: [
         "guava",
@@ -60,12 +83,13 @@
 }
 
 // File that contains the standard command line argumetns to hoststubgen.
+// This is only for the prototype. The productionized version is "ravenwood-standard-options".
 filegroup {
     name: "hoststubgen-standard-options",
+    defaults: ["hoststubgen-for-prototype-only-filegroup"],
     srcs: [
         "hoststubgen-standard-options.txt",
     ],
-    visibility: ["//visibility:public"],
 }
 
 hoststubgen_common_options = "$(location hoststubgen) " +
@@ -98,7 +122,6 @@
         "hoststubgen_keep_all.txt",
         "hoststubgen_dump.txt",
     ],
-    // visibility:  ["//visibility:public"],
 }
 
 // Generate the stub/impl from framework-all, with hidden APIs.
@@ -116,8 +139,10 @@
 }
 
 // Extract the stub jar from "framework-all-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
 java_genrule_host {
     name: "framework-all-hidden-api-host-stub",
+    defaults: ["hoststubgen-for-prototype-only-genrule"],
     cmd: "cp $(in) $(out)",
     srcs: [
         ":framework-all-hidden-api-host{host_stub.jar}",
@@ -125,12 +150,13 @@
     out: [
         "host_stub.jar",
     ],
-    visibility: ["//visibility:public"],
 }
 
 // Extract the impl jar from "framework-all-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
 java_genrule_host {
     name: "framework-all-hidden-api-host-impl",
+    defaults: ["hoststubgen-for-prototype-only-genrule"],
     cmd: "cp $(in) $(out)",
     srcs: [
         ":framework-all-hidden-api-host{host_impl.jar}",
@@ -138,11 +164,11 @@
     out: [
         "host_impl.jar",
     ],
-    visibility: ["//visibility:public"],
 }
 
 // Generate the stub/impl from framework-all, with only public/system/test APIs, without
 // hidden APIs.
+// This is only for the prototype. Do not use it in "productionized" build rules.
 java_genrule_host {
     name: "framework-all-test-api-host",
     defaults: ["hoststubgen-command-defaults"],
@@ -159,8 +185,10 @@
 }
 
 // Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
 java_genrule_host {
     name: "framework-all-test-api-host-stub",
+    defaults: ["hoststubgen-for-prototype-only-genrule"],
     cmd: "cp $(in) $(out)",
     srcs: [
         ":framework-all-test-api-host{host_stub.jar}",
@@ -168,12 +196,13 @@
     out: [
         "host_stub.jar",
     ],
-    visibility: ["//visibility:public"],
 }
 
 // Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
 java_genrule_host {
     name: "framework-all-test-api-host-impl",
+    defaults: ["hoststubgen-for-prototype-only-genrule"],
     cmd: "cp $(in) $(out)",
     srcs: [
         ":framework-all-test-api-host{host_impl.jar}",
@@ -181,7 +210,6 @@
     out: [
         "host_impl.jar",
     ],
-    visibility: ["//visibility:public"],
 }
 
 // This library contains helper classes to build hostside tests/targets.
@@ -191,6 +219,7 @@
 // Ideally this library should be empty.
 java_library_host {
     name: "hoststubgen-helper-framework-buildtime",
+    defaults: ["hoststubgen-for-prototype-only-java"],
     srcs: [
         "helper-framework-buildtime-src/**/*.java",
     ],
@@ -200,13 +229,13 @@
         "framework-all-hidden-api-host-impl",
         "junit",
     ],
-    visibility: ["//visibility:public"],
 }
 
 // This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
 // that are needed at runtime.
 java_library_host {
     name: "hoststubgen-helper-framework-runtime",
+    defaults: ["hoststubgen-for-prototype-only-java"],
     srcs: [
         "helper-framework-runtime-src/**/*.java",
     ],
@@ -214,7 +243,6 @@
         "hoststubgen-helper-runtime",
         "framework-all-hidden-api-host-impl",
     ],
-    visibility: ["//visibility:public"],
 }
 
 // Defaults for host side test modules.
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
index 4c37579..7ada961 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
@@ -17,8 +17,6 @@
 
 import static java.lang.annotation.ElementType.TYPE;
 
-import org.objectweb.asm.Type;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
 @Target({TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface HostStubGenProcessedKeepClass {
-    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class);
+    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class);
     String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
index 34e0030..e598da0a 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
@@ -17,8 +17,6 @@
 
 import static java.lang.annotation.ElementType.TYPE;
 
-import org.objectweb.asm.Type;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
 @Target({TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface HostStubGenProcessedStubClass {
-    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class);
+    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class);
     String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index d176b5e..9f83597 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -15,8 +15,6 @@
  */
 package com.android.hoststubgen.hosthelper;
 
-import org.objectweb.asm.Type;
-
 import java.io.PrintStream;
 import java.lang.StackWalker.Option;
 import java.lang.reflect.Method;
@@ -32,7 +30,15 @@
     private HostTestUtils() {
     }
 
-    public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class);
+    /**
+     * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM
+     * in this JAR.
+     */
+    public static String getInternalName(final Class<?> clazz) {
+        return clazz.getName().replace('.', '/');
+    }
+
+    public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);
 
     /** If true, we won't print method call log. */
     private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
index 33010ba..678e6ea 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
@@ -46,8 +46,11 @@
         }
         methodPolicy = policy
 
-        // TODO: Need to think about the realistic default behavior.
-        classPolicy = if (policy != FilterPolicy.Throw) policy else FilterPolicy.Remove
+        // If the default policy is "throw", we convert it to "keep" for classes and fields.
+        classPolicy = when (policy) {
+            FilterPolicy.Throw -> FilterPolicy.Keep
+            else -> policy
+        }
         fieldPolicy = classPolicy
     }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 9c372ff..c6334c4 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -17,6 +17,8 @@
 
 import com.android.hoststubgen.HostStubGenErrors
 import com.android.hoststubgen.HostStubGenInternalException
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
 import com.android.hoststubgen.asm.isAnonymousInnerClass
 import com.android.hoststubgen.log
 import com.android.hoststubgen.asm.ClassNodes
@@ -81,6 +83,16 @@
             }
         }
 
+        // If we throw from the static initializer, the class would be useless, so we convert it
+        // "keep" instead.
+        if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC &&
+            fallback.policy == FilterPolicy.Throw) {
+            // TODO Maybe show a warning?? But that'd be too noisy with --default-throw.
+            return FilterPolicy.Keep.withReason(
+                "'throw' on static initializer is handled as 'keep'" +
+                        " [original throw reason: ${fallback.reason}]")
+        }
+
         return fallback
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 57b6689..ce72a8e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -79,7 +79,7 @@
             // StaticInitMerger will merge it with the existing one, if any.
             visitMethod(
                 Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
-                "<clinit>",
+                CLASS_INITIALIZER_NAME,
                 "()V",
                 null,
                 null
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 43ceec4..0761edc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -372,7 +372,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -387,17 +387,6 @@
          x: ldc           #x                 // String Stub!
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
          x: athrow
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 43ceec4..0761edc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -372,7 +372,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -387,17 +387,6 @@
          x: ldc           #x                 // String Stub!
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
          x: athrow
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 079d2a8..8fcd2fb 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -15,3 +15,8 @@
 
 # Class load hook
 class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+
+
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer stubclass
+  # Testing 'throw' on a static initializer. This should be handled as 'keep'.
+  method <clinit>	()V	 throw
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index fd48646..523106f 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -18,8 +18,6 @@
 
 # This scripts run the "tiny-framework" test, but does most stuff from the command line, using
 # the native java and javac commands.
-# This is useful to
-
 
 debug=0
 while getopts "d" opt; do
@@ -38,7 +36,7 @@
 HOSTSTUBGEN=hoststubgen
 
 # Rebuild the tool and the dependencies. These are the only things we build with the build system.
-run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth-prebuilt junit
+run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth junit
 
 
 # Build tiny-framework
@@ -57,7 +55,7 @@
 
 test_compile_classpaths=(
   $SOONG_INT/external/junit/junit/android_common/combined/junit.jar
-  $SOONG_INT/prebuilts/tools/common/m2/truth-prebuilt/android_common/combined/truth-prebuilt.jar
+  $SOONG_INT/external/truth/truth/android_common/combined/truth.jar
 )
 
 test_runtime_classpaths=(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
index 53cfdf6..01a690b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
@@ -18,10 +18,14 @@
 import android.hosttest.annotation.HostSideTestClassLoadHook;
 import android.hosttest.annotation.HostSideTestWholeClassStub;
 
+
+// Note, policy-override-tiny-framework.txt hss an override on this class.
 @HostSideTestClassLoadHook(
         "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
 @HostSideTestWholeClassStub
 public class TinyFrameworkClassWithInitializer {
+    // Note, this method has a 'throw' in the policy file, which is handled as a 'keep' (because
+    // it's a static initializer), so this won't show up in the stub jar.
     static {
         sInitialized = true;
     }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 246d065..cd604ff 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -23,6 +23,10 @@
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
 public class TinyFrameworkClassTest {
     @Rule
     public ExpectedException thrown = ExpectedException.none();
@@ -140,4 +144,42 @@
         assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
                 .doesNotContain(TinyFrameworkNestedClasses.class);
     }
+
+    /**
+     * Test to try accessing JDK private fields using reflections + setAccessible(true),
+     * which is now disallowed due to Java Modules, unless you run the javacommand with.
+     *   --add-opens=java.base/java.io=ALL-UNNAMED
+     *
+     * You can try it from the command line, like:
+     * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testFileDescriptor() throws Exception {
+        var fd = FileDescriptor.out;
+
+        // Get the FD value directly from the private field.
+        // This is now prohibited due to Java Modules.
+        // It throws:
+        // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa
+
+        thrown.expect(java.lang.reflect.InaccessibleObjectException.class);
+
+        // Access the private field.
+        final Field f = FileDescriptor.class.getDeclaredField("fd");
+        final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class);
+        f.setAccessible(true);
+        m.setAccessible(true);
+
+        assertThat(f.get(fd)).isEqualTo(1);
+
+        // Set
+        f.set(fd, 2);
+        assertThat(f.get(fd)).isEqualTo(2);
+
+        // Call the package private method, set(int).
+        m.invoke(fd, 0);
+        assertThat(f.get(fd)).isEqualTo(0);
+    }
 }
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 7600942..8bc88de 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -33,6 +33,7 @@
 run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
 
 run ./hoststubgen/test-framework/run-test-without-atest.sh
+
 run ./hoststubgen/test-tiny-framework/run-test-manually.sh
 run atest tiny-framework-dump-test
 run ./scripts/build-framework-hostside-jars-and-extract.sh
diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp
index ca96559..40281d2 100644
--- a/tools/lint/global/integration_tests/Android.bp
+++ b/tools/lint/global/integration_tests/Android.bp
@@ -12,25 +12,58 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_library {
-    name: "AndroidGlobalLintTestNoAidl",
-    srcs: ["TestNoAidl/**/*.java"],
+// Integration tests for @EnforcePermission linters.
+// Each test defines its own java_library. The XML lint report from this
+// java_library is wrapped under a Python library with a unique pkg_path (this
+// is to avoid a name conflict for the report file). All the tests are
+// referenced and executed by AndroidGlobalLintCheckerIntegrationTest.
+
+java_defaults {
+    name: "AndroidGlobalLintIntegrationTestDefault",
     libs: [
         "framework-annotations-lib",
     ],
     lint: {
-        // It is expected that lint returns an error when processing this
+        // It is expected that lint returns an error when processing the
         // library. Silence it here, the lint output is verified in tests.py.
         suppress_exit_code: true,
     },
 }
 
+java_library {
+    name: "AndroidGlobalLintTestNoAidl",
+    srcs: ["TestNoAidl/**/*.java"],
+    defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+    name: "AndroidGlobalLintTestNoAidl_py",
+    data: [":AndroidGlobalLintTestNoAidl{.lint}"],
+    pkg_path: "no_aidl",
+}
+
+java_library {
+    name: "AndroidGlobalLintTestMissingAnnotation",
+    srcs: [
+        "TestMissingAnnotation/**/*.java",
+        "TestMissingAnnotation/**/*.aidl",
+    ],
+    defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+    name: "AndroidGlobalLintTestMissingAnnotation_py",
+    data: [":AndroidGlobalLintTestMissingAnnotation{.lint}"],
+    pkg_path: "missing_annotation",
+}
+
 python_test_host {
     name: "AndroidGlobalLintCheckerIntegrationTest",
     srcs: ["tests.py"],
     main: "tests.py",
-    data: [
-        ":AndroidGlobalLintTestNoAidl{.lint}",
+    libs: [
+        "AndroidGlobalLintTestNoAidl_py",
+        "AndroidGlobalLintTestMissingAnnotation_py",
     ],
     version: {
         py3: {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
index 0aa6b0b..9e4854c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.base.interactor
+package com.google.android.lint.integration_tests;
 
-data class QSTileDataRequest(
-    val userId: Int,
-    val trigger: StateUpdateTrigger,
-)
+/**
+ * A class that implements an AIDL interface, but is missing the @EnforcePermission annotation.
+ */
+class TestMissingAnnotation extends IFoo.Stub {
+
+    @Override
+    public void Method() {
+    }
+
+}
diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
new file mode 100644
index 0000000..95ec2c2
--- /dev/null
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
@@ -0,0 +1,7 @@
+package com.google.android.lint.integration_tests;
+
+interface IFoo {
+
+    @EnforcePermission("INTERNET")
+    void Method();
+}
diff --git a/tools/lint/global/integration_tests/tests.py b/tools/lint/global/integration_tests/tests.py
index fc3eeb4..cdb16b8 100644
--- a/tools/lint/global/integration_tests/tests.py
+++ b/tools/lint/global/integration_tests/tests.py
@@ -19,16 +19,28 @@
 class TestLinterReports(unittest.TestCase):
     """Integration tests for the linters used by @EnforcePermission."""
 
-    def test_no_aidl(self):
-        report = pkgutil.get_data("lint", "lint-report.xml").decode()
+    def _read_report(self, pkg_path):
+        report = pkgutil.get_data(pkg_path, "lint/lint-report.xml").decode()
         issues = xml.etree.ElementTree.fromstring(report)
         self.assertEqual(issues.tag, "issues")
+        return issues
+
+    def test_no_aidl(self):
+        issues = self._read_report("no_aidl")
         self.assertEqual(len(issues), 1)
 
         issue = issues[0]
         self.assertEqual(issue.attrib["id"], "MisusingEnforcePermissionAnnotation")
         self.assertEqual(issue.attrib["severity"], "Error")
 
+    def test_missing_annotation(self):
+        issues = self._read_report("missing_annotation")
+        self.assertEqual(len(issues), 1)
+
+        issue = issues[0]
+        self.assertEqual(issue.attrib["id"], "MissingEnforcePermissionAnnotation")
+        self.assertEqual(issue.attrib["severity"], "Error")
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 2a199d2..58638e8 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -113,6 +113,7 @@
     private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
     private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
     private Runnable mDeathEventHandler;
+    private Object mLock = new Object();
     /**
      * Ensures that no more than one sendMgmtFrame operation runs concurrently.
      */
@@ -625,13 +626,15 @@
     @VisibleForTesting
     public void binderDied() {
         mEventHandler.post(() -> {
-            Log.e(TAG, "Wificond died!");
-            clearState();
-            // Invalidate the global wificond handle on death. Will be refreshed
-            // on the next setup call.
-            mWificond = null;
-            if (mDeathEventHandler != null) {
-                mDeathEventHandler.run();
+            synchronized (mLock) {
+                Log.e(TAG, "Wificond died!");
+                clearState();
+                // Invalidate the global wificond handle on death. Will be refreshed
+                // on the next setup call.
+                mWificond = null;
+                if (mDeathEventHandler != null) {
+                    mDeathEventHandler.run();
+                }
             }
         });
     }
@@ -867,26 +870,28 @@
     * @return Returns true on success.
     */
     public boolean tearDownInterfaces() {
-        Log.d(TAG, "tearing down interfaces in wificond");
-        // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
-        // could be used to cleanup before we setup any interfaces.
-        if (!retrieveWificondAndRegisterForDeath()) {
+        synchronized (mLock) {
+            Log.d(TAG, "tearing down interfaces in wificond");
+            // Explicitly refresh the wificond handler because |tearDownInterfaces()|
+            // could be used to cleanup before we setup any interfaces.
+            if (!retrieveWificondAndRegisterForDeath()) {
+                return false;
+            }
+
+            try {
+                for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+                    entry.getValue().unsubscribeScanEvents();
+                    entry.getValue().unsubscribePnoScanEvents();
+                }
+                mWificond.tearDownInterfaces();
+                clearState();
+                return true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+            }
+
             return false;
         }
-
-        try {
-            for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
-                entry.getValue().unsubscribeScanEvents();
-                entry.getValue().unsubscribePnoScanEvents();
-            }
-            mWificond.tearDownInterfaces();
-            clearState();
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to tear down interfaces due to remote exception");
-        }
-
-        return false;
     }
 
     /** Helper function to look up the interface handle using name */
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f1591..3d5a0f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.sharedconnectivity.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -169,6 +170,7 @@
          * @return Returns the Builder object.
          */
         @NonNull
+        @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
         public Builder setBatteryCharging(boolean isBatteryCharging) {
             mIsBatteryCharging = isBatteryCharging;
             return this;
@@ -283,6 +285,7 @@
      *
      * @return Returns true if the battery of the remote device is charging.
      */
+    @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
     public boolean isBatteryCharging() {
         return mIsBatteryCharging;
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 71ac94b..b0f68f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -17,6 +17,7 @@
 package android.net.wifi.sharedconnectivity.app;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -296,6 +297,7 @@
      */
     @TestApi
     @NonNull
+    @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
     public BroadcastReceiver getBroadcastReceiver() {
         return mBroadcastReceiver;
     }